Compare commits

..

127 Commits

Author SHA1 Message Date
Torsten Dreyer
489573329e new version: 2018.2.0 2018-02-18 21:22:54 +01:00
Torsten Dreyer
843df4b334 new version: 2018.1.1 2018-02-18 21:22:54 +01:00
Erik Hofman
ef1821c025 Switch to C++11 2018-02-16 16:09:26 +01:00
James Turner
ed5372f439 Add new PROTECTED property attribute 2018-02-04 17:10:41 +00:00
James Turner
bc63381fb0 Packages: tweaks to thumbnail cache
Don’t discard the existing data when expiring from the cache, in case
the refresh request fails.
2018-01-31 15:00:58 +00:00
James Turner
c59f2a0749 Fix Windows _wutime compilation 2018-01-31 13:10:34 +00:00
James Turner
5c30ca5dc6 Add SGPath::touch() helper 2018-01-31 11:04:10 +00:00
Thomas Geymayer
37bc43c7aa canvas::Path: support for stroke-opacity and fill-opacity 2018-01-29 09:07:04 +01:00
Thomas Geymayer
4edd3f45b7 canvas: Refactor and protect against expired pointers 2018-01-28 15:57:14 +01:00
Thomas Geymayer
087ea4c5f3 canvas: Refactor with C++11 2018-01-28 00:49:05 +01:00
Florent Rougon
40534d6316 Remove overzealous, invasive behavior from SGBinding's destructor
Since the dawn of times (FG commit
1bcaf4bfdd38f18ac7c375dd9319935ff3df56ac, where SGBinding was called
FGBinding), SGBinding's destructor has had a strange behavior:

SGBinding::~SGBinding()
{
  if(_arg && _arg->getParent())
    _arg->getParent()->removeChild(_arg->getName(), _arg->getIndex());
}

In other words, it used to remove the passed-in <binding> node from its
parent node (if any) once the SGBinding instance got destroyed. This
behavior is very unintuitive to several people and has resulted in a few
workarounds in the FG code base just to cope with this strangeness.

This commit gives SGBinding the implictly-generated destructor,
therefore SGBinding::~SGBinding() does not attempt to remove the
property node anymore.

See disussion at:

  https://sourceforge.net/p/flightgear/mailman/flightgear-devel/thread/87d12b1h0h.fsf%40frougon.crabdance.com/#msg36190666
2018-01-27 11:03:06 +01:00
James Turner
8621925e75 Floating-point comparison helpers for SGMath<T>
Implemented using numeric_limits::epsilon and std::abs, fingers
crossed this is sufficiently portable!
2018-01-26 11:29:18 +00:00
Thomas Geymayer
5cb2360985 cppbind: Use separet Context::to_nasal_vec instead of overload 2018-01-23 16:15:56 +01:00
Thomas Geymayer
b831d2b64a cppbind: Fix for old g++ 2018-01-23 14:02:18 +01:00
Thomas Geymayer
e81597a176 cppbind: Opimize convert fixed size arrays and more type_traits 2018-01-23 09:30:00 +01:00
James Turner
7df4f32de6 Merge /u/janodesbois/simgear-jano/ branch mp-merge into next
https://sourceforge.net/p/flightgear/simgear/merge-requests/42/
2018-01-22 09:48:01 +00:00
Thomas Geymayer
db3725880e cppbind: simplify with 'auto' 2018-01-21 14:39:24 +01:00
Thomas Geymayer
af5833cbc5 cppbind: Refactor to use C++11+ type_traits 2018-01-21 13:21:55 +01:00
James Turner
9078a085c3 Kill exception if removeDelegate fails
This simplifies logic when changing the package::Root
2018-01-20 16:19:13 +00:00
jean pellotier
396a7d7f09 a system clock from simgear, using std::chrono libs
this is to use some kind of utc time for timestamps in mp protocol,
to improve the lag correction system for mp planes, using a
"real time" mode when possible.
2018-01-20 09:15:56 +01:00
Thomas Geymayer
8a128a57cd cppbind: forward declarations 2018-01-19 10:34:37 +01:00
Thomas Geymayer
3dca9b8a68 cppbind: Make Mac and Windows happy and add forward declaration 2018-01-19 10:24:33 +01:00
Thomas Geymayer
5f8f5a1c33 cppbind: More refactoring and finally getting rid of boost.preprocessor 2018-01-19 09:12:11 +01:00
Thomas Geymayer
5a72a7d9f4 canvas: Refactor Path::addSegment with std::initializer_list 2018-01-16 09:01:55 +01:00
James Turner
51a91bfa8a Ignore CmakeList.txt.user files 2018-01-12 22:19:54 +00:00
Thomas Geymayer
4cedd0a346 cppbind: refactor Ghost::method to use variadic templates 2018-01-12 08:32:57 +01:00
Thomas Geymayer
b989f4085d Various doxygen improvements/fixes. 2018-01-12 08:32:04 +01:00
Thomas Geymayer
19cc797c41 cppbind: fix exporting integer_sequence and small codingstyle fixes 2018-01-12 07:57:17 +01:00
Thomas Geymayer
99ee3d9dec cppbind: nasal::Context refactoring and doxygen improvements. 2018-01-09 22:50:29 +01:00
Florent Rougon
455753c774 Simplify code in NetChannelPoller::removeChannel()
Remove an apparently bogus portability workaround (which was presumably
targetting one of the bugs fixed in the previous commit [1]) and further
simplify the code using std::find().

[1] da099d4312/
2018-01-08 10:26:18 +01:00
James Turner
da099d4312 Correct use of remove_if in the test-server
Should fix erratic crashes of some tests.
2018-01-07 16:10:50 +00:00
xDraconian
a88b9181b6 Enable the test 'test_precipitation' 2018-01-04 08:18:43 +01:00
xDraconian
7f7c632914 Simgear::test_precipitation test coverage
Lines:     0.0% >> 92.3%
Functions: 0.0% >> 97.1%

Memory leaks resolved - reclaiming 169kb in 126 blocks
No memory leaks remain
2018-01-03 12:40:49 +00:00
Alessandro Menti
e7356223bb CMakeLists.txt: explicitly enable the CMP0067 (C/C++ standard flags) policy
Explicitly enable the CMP0067 policy to have try_compile use the correct
C/C++ standard flags; otherwise, CMake will default to not honoring those,
causing the C/C++ checks to be compiled with no standard flags and SimGear
to be compiled with them. This causes errors if we try to detect a new
prototype which is only present in C++14 and above).
2018-01-03 11:54:04 +01:00
Florent Rougon
7547ad0391 Improve formatting of the HAVE_WORKING_STD_REGEX test
Indent the test code in a nicer way and shorten it, taking advantage of
this guarantee from the C++ standard:

  If control reaches the end of main without encountering a return
  statement, the effect is that of executing return 0.

-> no need for "#include <cstdlib>" nor for "return EXIT_SUCCESS".
2018-01-03 00:34:52 +01:00
Thomas Geymayer
ca30d6bb3d Add missing simgear_config.h include
Required for HAVE_STD_INDEX_SEQUENCE to be properly set.
2018-01-02 11:37:46 +01:00
Thomas Geymayer
ebd2cdb7a7 SGPickAnimation: fix member initialization order 2018-01-02 09:13:14 +01:00
Thomas Geymayer
52b8f60953 Add std::index_sequence for C++11
Will be required for nasal::Ghost
2018-01-02 08:00:49 +01:00
Florent Rougon
ab1e2d49ab Add a CMake check for a working <regex> implementation
Define HAVE_WORKING_STD_REGEX if, and only if <regex> is usable.

Normally, <regex> should be available and working in any compliant C++11
implementation, however at least g++ 4.8[1] lies about its C++11
compliance: its <regex> is utterly unusable, see [2] and [3] for
details.

[1] Which appears to be (precisely 4.8.5) the version shipped in
    CentOS 7, and used on FlightGear's current Jenkins installation.

[2] https://stackoverflow.com/a/12665408/4756009

[3] https://sourceforge.net/p/flightgear/mailman/message/36170781/
2017-12-29 13:46:42 +01:00
xDraconian
401bd1c643 Simgear::test_bucket code coverage
Lines:     82.1% >>  98.7%
Functions: 83.0% >> 100.0%

No memory leaks
2017-12-23 08:52:23 +00:00
xDraconian
53e6bec425 Tests: resolve several memory leaks 2017-12-17 10:06:20 +01:00
James Turner
f6ded69fa3 Untar failure path fixes
By xDraconian, ensure we call inflateEnd in failure cases, otherwise
the inflate context is leaked.
2017-12-11 18:14:40 +00:00
Thomas Geymayer
68053d64b5 function_list<>: Simplify with C++11 2017-12-04 08:47:51 +01:00
Richard Harrison
1f12966628 Add condition to knob animation.
This will prevent both input from being received and the position being updated by property change.
2017-12-04 07:53:39 +01:00
Richard Harrison
8d3723c91f Prevent nullptr exception in treenodes.
Happened to me once or twice mainly during reposition probably as scenery changed whilst still loading.
2017-12-04 07:33:45 +01:00
Richard Harrison
7dcc359110 add condition to pick animations 2017-12-04 07:31:59 +01:00
Richard Harrison
854881bba2 Prevent nullptr exception. Had this happen once. 2017-12-04 07:30:48 +01:00
Alessandro Menti
ddb7958f40 Remove old .spec files
Remove old SuSE/Red Hat packaging files (in line with what was done
in commit ef66ba in FlightGear).
2017-12-02 17:47:05 +01:00
Alessandro Menti
fe0d3fd448 Remove the dependency on the UTF-8 external library
Remove the dependency on the UTF-8 external library by writing the
UTF-8/code point conversion routines in KeyboardEvent.cxx.
2017-12-02 16:32:27 +01:00
Thomas Geymayer
15d5c878f3 canvas::NasalWidget: don't call unsafe method in destructor
Calling a virtual method in a destructor has undefined behaviour.
Furthermore passing 'this' to nasal from within the destructor
is not safe. 'onRemove' is called for widgets within layouts
on destruction anyhow, so no need to call it here again. If widgets
are arranged manually without any layout 'onRemoved' should be
called upon removing them from a dialog.
2017-12-01 08:14:44 +01:00
Thomas Geymayer
4f63c3f8a8 nasal::Object: Simplify with C++11
C++11 variadic templates finally allow us to get rid of the
ugly boost preprocessor workaround.
2017-12-01 07:32:49 +01:00
James Turner
b766ce76ff Package thumbnail caching tweaks
Resolves some issue with expired thumbnails not being reloaded
correctly.
2017-11-27 23:37:56 +00:00
ThorstenB
0b4b09958f Merge /u/thbr/simgear/ branch next into next
https://sourceforge.net/p/flightgear/simgear/merge-requests/41/
2017-11-24 21:58:52 +00:00
Florent Rougon
e12fc5a89d Use common definition of simgear::enumValue() from <simgear/sg_inlines.h>
This avoids code duplication.
2017-11-19 08:15:44 +01:00
Florent Rougon
bd3c351f1f Add function template simgear::enumValue() to simgear/sg_inlines.h
This function template is useful to get the value of a member of a
scoped enumeration, without having to hardcode its type while casting
it.
2017-11-19 00:45:47 +01:00
Florent Rougon
6064be33e5 SGPath: add comparison operators (<, >, <=, >=) and an std::hash specialization
This allows one to use SGPath in containers such as std::map,
std::unordered_map and std::unordered_set.

Like the existing == and !=, all these operators rely solely on the
UTF-8 internal representation of the path.
2017-11-19 00:45:47 +01:00
Florent Rougon
1540d6f472 SGPath: mark the str() and utf8Str() methods as 'noexcept'
We'll use this to cleanly declare std::hash<SGPath> as 'noexcept' in the
next commit, which appears to be desirable for std::hash template
specializations, according to:

  http://en.cppreference.com/w/cpp/utility/hash
2017-11-18 16:04:32 +01:00
Florent Rougon
1310092c0c SGPath: enable move operations
This is done by simply not user-defining the copy constructor,
copy-assignment operator and destructor. See [1] for more info.

[1] http://accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf

For the benchmark below (compiled with the next commit to allow sorting
SGPath instances), on Linux amd64 with g++ 6.3.0, I observe that
enabling SGPath move operations with this commit increases the
performance by 31% or 28% respectively, depending on whether I use this:

  // Typical code that creates a data structure in several steps and
  // benefits from move operations (the std::move() does nothing when
  // running the test with move operations disabled: a copy is made).
  auto p = SGPath::fromUtf8(randomString(0, 30));
  v.emplace_back(std::move(p));

or that:

  v.emplace_back(randomString(0, 30))

for the initialization code. Now the benchmark code:

using std::string;

static std::default_random_engine randomNumbersGenerator;

// Utility function: generate a random string whose length is in the
// [minLen, maxLen] range.
string randomString(string::size_type minLen, string::size_type maxLen)
{
  std::uniform_int_distribution<string::size_type> sLenDist(minLen, maxLen);
  std::uniform_int_distribution<int> byteDist(0, 255);
  auto randomByte = std::bind(byteDist, randomNumbersGenerator);

  string::size_type len = sLenDist(randomNumbersGenerator);
  string str;

  while (str.size() < len) {
    str += std::char_traits<char>::to_char_type(randomByte());
  }

  return str;
}

// The test function, run with nbIterations = 500000, minSize = 0 and
// maxSize = 200 to obtain the figures given above.
void SGPath_perfTest(std::size_t nbIterations,
                     std::size_t minSize, std::size_t maxSize)
{
  std::uniform_int_distribution<std::size_t> sizeDist(minSize, maxSize);
  auto randomSize = std::bind(sizeDist, randomNumbersGenerator);

  std::chrono::time_point<std::chrono::system_clock> start, end;
  start = std::chrono::system_clock::now();

  vector<SGPath> v;

  for (std::size_t i=0; i < nbIterations; i++) {
    v = vector<SGPath>{};          // start anew

    for (std::size_t j=0; j < randomSize(); j++) {
      v.emplace_back(randomString(0, 30));
    }

    std::shuffle(v.begin(), v.end(), randomNumbersGenerator);
    std::sort(v.begin(), v.end());
  }

  end = std::chrono::system_clock::now();
  std::chrono::duration<double> elapsedSecs = end - start;
  std::cout << elapsedSecs.count() << "\n"; // duration in seconds
}
2017-11-18 14:54:33 +01:00
ThorstenB
e485fac8ed Provide polar earth radius as a simgear constant. 2017-11-16 21:38:50 +01:00
Florent Rougon
55d75f18de Add missing include <algorithm> for std::max() and std::min()
This include was missing in several files from the simgear/hla
directory.
2017-11-16 16:00:14 +01:00
Florent Rougon
e8648a3f71 Add missing include <algorithm> for std::max() 2017-11-16 00:42:22 +01:00
Florent Rougon
0c7cabe46f SGSharedPtr: optimized version of the free function swap()
With this simple change, the speedup as compared to commit 18f048424 is
now 37 % for the benchmark given in the previous commit. This is because
optimized swap() only needs to swap the raw pointers, which is certainly
less work than the three move assignments on SGSharedPtr (not raw
pointers) done by std::swap().

To benefit from this, write code like:

  using std::swap;  // now useless for SGSharedPtr, but idiomatic
  swap(ptr1, ptr2); // *not* std::swap()!
2017-11-14 01:31:03 +01:00
Florent Rougon
a9ec3be2fd SGSharedPtr: the move constructor and move assignment operator are now 'noexcept'
This automatically makes SGSharedPtr more efficient when used in
standard containers (among others). See below for the benchmark details.

Mark as 'noexcept' (after checking it's legitimate!) the SGSharedPtr and
SGReferenced methods required for SGSharedPtr's move constructor and
move assignment operator to be guaranteed 'noexcept'.

Benchmark
---------

I measured a 25 % speedup with g++ 6.3.0 on Linux amd64, CFLAGS=-Wall -O2
as compared to commit 18f0484249 (which is
just before my changes to SGSharedPtr.hxx) on the following test code,
called with:

  nbIterations = 3000000
  minSize      =       0
  maxSize      =     200

------------------------------------------------------------------------
static std::default_random_engine randomNumbersGenerator;

class SGReferencedTestClass : public SGReferenced
{ int i; };

void SGSharedPtr_perfTest(std::size_t nbIterations,
                          std::size_t minSize, std::size_t maxSize)
{
  using Ref = SGSharedPtr<SGReferencedTestClass>;

  std::uniform_int_distribution<std::size_t> sizeDist(minSize, maxSize);
  auto randomSize = std::bind(sizeDist, randomNumbersGenerator);

  std::chrono::time_point<std::chrono::system_clock> start, end;
  start = std::chrono::system_clock::now();

  std::vector<Ref> v;

  for (std::size_t i=0; i < nbIterations; i++) {
    v = std::vector<Ref>{};          // start anew

    for (std::size_t j=0; j < randomSize(); j++) {
      auto p = Ref(new SGReferencedTestClass());
      v.emplace_back(std::move(p));
    }

    std::shuffle(v.begin(), v.end(), randomNumbersGenerator);
    std::sort(v.begin(), v.end());
  }

  end = std::chrono::system_clock::now();
  std::chrono::duration<double> elapsedSecs = end - start;
  std::cout << elapsedSecs.count() << "\n"; // duration in seconds
}
------------------------------------------------------------------------

Basically, these gains can be explained by the fact that copying an
SGSharedPtr requires to test SGReferenced::ref, increase the refcount,
and then when the object is destroyed, test again SGReferenced::ref,
decrease the refcount and test it in order to maybe delete. With the
move constructor and move assignment operator, copying the argument is
never necessary: its raw pointer can be swapped with the one contained
in *this, which is very fast. For the move constructor, this is all that
is needed; move assignment just needs one reset() call after that in
order to release the resource from the moved-from shared pointer.
2017-11-13 23:43:43 +01:00
Florent Rougon
abaaee1af2 Add simgear::noexceptSwap() to simgear/sg_inlines.h
This is a function template that is guaranteed to be 'noexcept' as long
as compilation succeeds. Idea and implementation from
<https://akrzemi1.wordpress.com/2011/06/10/using-noexcept/>.
2017-11-13 11:59:11 +01:00
Florent Rougon
6283a515b9 Fix missing headers in simgear/io/DNSClient.cxx and simgear/props/props_test.cxx
<algorithm> is needed for std::sort() and std::find().
2017-11-13 10:04:34 +01:00
Thomas Geymayer
ce7d463710 Fix wrong argument
One more catch by Florent Rougon.
2017-11-13 08:22:14 +01:00
Florent Rougon
bc3404fcbe SGSharedPtr: more efficient copy and move assignment operators
The copy-and-swap idiom is certainly very cute, but often causes
unnecessary copies. My commit fedafb9352
did exactly that, unfortunately.

Restore the exact same code for the copy-assignment operator as before
commit fedafb935, and add a more efficient implementation for the
move-assignment operator.

As explained by Howard Hinnant in [1] and [2], if some particular piece
of code really needs a strong exception safety guarantee, one can easily
add a specific method for that; this is not a valid reason to make the
code slower for all other places that have no use for such a guarantee!

[1] http://www.slideshare.net/ripplelabs/howard-hinnant-accu2014
[2] https://stackoverflow.com/a/9322542/4756009
2017-11-13 07:34:48 +01:00
Thomas Geymayer
bd87d3963a Fix leak in NasalObjectHolder
Thanks to Florent Rougon for spotting it.
2017-11-12 22:41:14 +01:00
Richard Harrison
fed449a801 Fix error message in axis object animation 2017-11-12 17:07:29 +01:00
Florent Rougon
19dd92d3e0 Remove or replace obsolete uses of throw()
In C++11, destructors are 'noexcept' by default -> remove useless
throw() specifiers. There was one case that wasn't about a destructor: I
replaced the 'throw()' with 'noexcept' because this use of 'throw()' is
deprecated and 'noexcept' offers the intended meaning as far as I can
guess (in C++17, 'throw()' will be equivalent to 'noexcept' anyway). For
more info, see:

  http://en.cppreference.com/w/cpp/language/noexcept_spec
  https://akrzemi1.wordpress.com/2013/08/20/noexcept-destructors/
2017-11-12 09:56:26 +01:00
Florent Rougon
f22b9ba9f1 SGSharedPtr: add unit tests
Add unit tests for move ctor and move assignment operator, as well for a
few other SGSharedPtr methods.
2017-11-11 17:55:54 +01:00
Florent Rougon
fedafb9352 SGSharedPtr: add move constructor and move assignment operator 2017-11-11 16:18:36 +01:00
Florent Rougon
18f0484249 Convert structure/shared_ptr_test.cpp to use the SG test macros (no more boost) 2017-11-11 01:29:49 +01:00
James Turner
10956056b3 Own code for UTF-32 <-> UTF-8 conversion
Avoids codecvt dependency on Unix where it might not be present, eg
with GCC 4.8; on Windows we use <codecvt> since it’s present in VS2015
to avoid writing a seperate UTF-16 <-> UTF-8 conversion.
2017-11-01 17:04:56 +00:00
Erik Hofman
e482f04123 int16_t needs cstdint 2017-10-31 11:22:39 +01:00
Erik Hofman
db89f0d4d1 Oops, use the proper project name 2017-10-31 10:49:04 +01:00
Erik Hofman
2ac97a9f1f std::wstring_convert requires locale 2017-10-31 10:47:12 +01:00
Erik Hofman
c03359a189 Update to the (now GPL) AeonWave version 3.0+ 2017-10-31 10:42:44 +01:00
Erik Hofman
84b636debc Add a frequency filter and a bitcrusher filetr 2017-10-31 10:42:16 +01:00
James Turner
2642299d77 We require C++11 now, simplify this code.
This ensures wstring to std::string conversion is always available,
needed for some HID work I’m doing on a branch,.
2017-10-29 13:20:39 +00:00
Richard Harrison
2a60e5e338 Added touch animation.
Designed for 2d objects, such as a canvas placements, this permits the receipt of touch (mouse click) events to enable the simulation of avionics with a touchscreen.

The coordinates are passed in as arguments to the action; these can be accessed with Nasal via the cmdarg() method.

example:

    <animation>
         <type>touch</type>
         <visible>true</visible>
         <object-name>VSDImage</object-name>
         <action>
             <touch>0</touch>
             <repeatable>false</repeatable>
             <binding>
                 <command>nasal</command>
                 <script>print("touch input
(",cmdarg().getNode("x").getValue(),",",cmdarg().getNode("y").getValue())</script>
             </binding>
         </action>
     </animation>
2017-10-29 01:04:36 +02:00
Florent Rougon
880c063d04 Remove useless readdir() calls in Dir::isEmpty()
simgear::Dir::isEmpty() used to make up to 5 calls to readdir(), while 3
are enough to say whether the directory has entries other than '.' and
'..'.

Also add an automated test for this method.
2017-10-27 20:49:17 +02:00
Florent Rougon
7a374c43dc Fix CMake test for std::isnan()
It used the wrong header; std::isnan() is defined in <cmath>.
2017-10-23 19:29:07 +02:00
Richard Harrison
7be1fcc32e Add method to copy entire properry subtree 2017-10-15 16:54:36 +02:00
Stuart Buchanan
b57dca66be Handle case where no tranform matrix exists. 2017-10-13 17:35:21 +01:00
Florent Rougon
d455f5f445 props_io.cxx: use SG_ORIGIN when throwing exceptions
This will provide more accurate info than the fixed string "SimGear
Property Reader".
2017-10-10 20:15:54 +02:00
Florent Rougon
2200fad30e Rename the ResourceProxy class to EmbeddedResourceProxy
This is done so as to avoid confusion with the unrelated classes
ResourceProvider and ResourceManager already present in SimGear.

Despite this new name, EmbeddedResourceProxy is a proxy not only for
embedded resources, but also for real files (hence the initial name
choice): its purpose is precisely to allow zero-work switching from one
data source to the other.
2017-10-07 16:36:49 +02:00
Florent Rougon
e5e112c3c2 Add a ResourceProxy class
The ResourceProxy class allows one to access real files or embedded
resources in a unified way. When using it, one can switch from one data
source to the other with minimal code changes, possibly even at runtime
(in which case there is obviously no code change at all).

Sample usage (from FlightGear for the globals->get_fg_root() bit):

  simgear::ResourceProxy proxy(globals->get_fg_root(), "/FGData");
  std::string s = proxy.getString("/some/path");
  std::unique_ptr<std::istream> streamp = proxy.getIStream("/some/path");

The methods ResourceProxy::getString(const std::string& path) and
ResourceProxy::getIStream(const std::string& path) decide whether to use
embedded resources or real files depending on the boolean value passed
to ResourceProxy::setUseEmbeddedResources() (also available as an
optional parameter to the ResourceProxy constructor, defaulting to
true). It is often most convenient to set this boolean once and don't
worry about it anymore---it's stored inside the ResourceProxy object.
Otherwise, if you want to fetch resources some times from real files,
other times from embedded resources, you may use the following methods:

  // Retrieve contents using embedded resources
  std:string s = proxy.getString("/some/path", true);
  std:string s = proxy.getStringDecideOnPrefix(":/some/path");

  // Retrieve contents using real files
  std:string s = proxy.getString("/some/path", false);
  std:string s = proxy.getStringDecideOnPrefix("/some/path");

(alternatively, you could use several ResourceProxy objects with
different values for the constructor's third parameter)
2017-10-01 07:54:17 +02:00
James Turner
dd3cdf63e6 Terrasync: Fix exception deleting orphaned directories. 2017-09-29 12:47:42 +01:00
Stuart Buchanan
a800189c25 Add support for Slippy Map Canvas layers.
- Add so-called Web Mercator projection and ability to choose
  projection for a Canvas Map.
- Add supporting functions for Slippy Maps (i.e. OSM)
2017-09-28 15:36:48 +01:00
James Turner
b342245619 Fix lib64 mode on Ubuntu 17.04 / CMake 3.7 2017-09-28 15:11:09 +01:00
James Turner
6cd5ac2d0d Move readTime (parse_time) from options.cxx 2017-09-27 15:06:37 +01:00
James Turner
872a48dacb Extend the state-machine parsing test slightly 2017-09-27 14:33:57 +01:00
James Turner
721aa544c9 Use C++11 in the state-machine code 2017-09-26 16:39:04 +02:00
Richard Harrison
7bfbda7188 Fix for AMD / Radeon rendering of lights as point sprites.
Tested against R9-290 and GTX 550 Ti (thanks Nikolai V. Chr.)
2017-09-24 22:10:44 +02:00
James Turner
602244979b Tar extractor can filter file paths. 2017-09-21 17:47:28 +01:00
Torsten Dreyer
8e29cae309 new version: 2017.4.0 2017-09-17 12:14:00 +02:00
Torsten Dreyer
2a73e6c0d5 new version: 2017.3.1 2017-09-17 12:14:00 +02:00
Richard Harrison
16b5dd5e78 Remove message "Could not find at least one of the following objects for axis animation" when not relevant.
By default you can either do

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

See discussion at <https://sourceforge.net/p/flightgear/mailman/message/35875305/>
2017-06-04 16:51:27 +02:00
Automatic Release Builder
679d4e1dcd new version: 2017.3.0 2017-05-18 15:15:48 +02:00
173 changed files with 7534 additions and 5472 deletions

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ CTestTestfile.cmake
install_manifest.txt
build*
Build
CMakeLists.txt.user

View File

@@ -2,8 +2,6 @@ if (NOT SYSTEM_EXPAT)
add_subdirectory(expat)
endif()
add_subdirectory(utf8)
if (ENABLE_DNS AND NOT SYSTEM_UDNS)
add_subdirectory(udns)
endif()

View File

@@ -1,17 +0,0 @@
include (SimGearComponent)
set(HEADERS
source/utf8.h
)
set(HEADERS_utf8
source/utf8/checked.h
source/utf8/core.h
source/utf8/unchecked.h
)
set(SOURCES
)
simgear_component(utf8 3rdparty/utf8 "${SOURCES}" "${HEADERS}")
simgear_component(utf8-internal 3rdparty/utf8/utf8 "" "${HEADERS_utf8}")

View File

@@ -1,12 +0,0 @@
utf8 cpp library
Release 2.3.4
A minor bug fix release. Thanks to all who reported bugs.
Note: Version 2.3.3 contained a regression, and therefore was removed.
Changes from version 2.3.2
- Bug fix [39]: checked.h Line 273 and unchecked.h Line 182 have an extra ';'
- Bug fix [36]: replace_invalid() only works with back_inserter
Files included in the release: utf8.h, core.h, checked.h, unchecked.h, utf8cpp.html, ReleaseNotes

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +0,0 @@
// Copyright 2006 Nemanja Trifunovic
/*
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731
#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731
#include "utf8/checked.h"
#include "utf8/unchecked.h"
#endif // header guard

View File

@@ -1,327 +0,0 @@
// Copyright 2006 Nemanja Trifunovic
/*
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
#include "core.h"
#include <stdexcept>
namespace utf8
{
// Base for the exceptions that may be thrown from the library
class exception : public ::std::exception {
};
// Exceptions that may be thrown from the library functions.
class invalid_code_point : public exception {
uint32_t cp;
public:
invalid_code_point(uint32_t cp) : cp(cp) {}
virtual const char* what() const throw() { return "Invalid code point"; }
uint32_t code_point() const {return cp;}
};
class invalid_utf8 : public exception {
uint8_t u8;
public:
invalid_utf8 (uint8_t u) : u8(u) {}
virtual const char* what() const throw() { return "Invalid UTF-8"; }
uint8_t utf8_octet() const {return u8;}
};
class invalid_utf16 : public exception {
uint16_t u16;
public:
invalid_utf16 (uint16_t u) : u16(u) {}
virtual const char* what() const throw() { return "Invalid UTF-16"; }
uint16_t utf16_word() const {return u16;}
};
class not_enough_room : public exception {
public:
virtual const char* what() const throw() { return "Not enough space"; }
};
/// The library API - functions intended to be called by the users
template <typename octet_iterator>
octet_iterator append(uint32_t cp, octet_iterator result)
{
if (!utf8::internal::is_code_point_valid(cp))
throw invalid_code_point(cp);
if (cp < 0x80) // one octet
*(result++) = static_cast<uint8_t>(cp);
else if (cp < 0x800) { // two octets
*(result++) = static_cast<uint8_t>((cp >> 6) | 0xc0);
*(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80);
}
else if (cp < 0x10000) { // three octets
*(result++) = static_cast<uint8_t>((cp >> 12) | 0xe0);
*(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);
*(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80);
}
else { // four octets
*(result++) = static_cast<uint8_t>((cp >> 18) | 0xf0);
*(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f) | 0x80);
*(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);
*(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80);
}
return result;
}
template <typename octet_iterator, typename output_iterator>
output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement)
{
while (start != end) {
octet_iterator sequence_start = start;
internal::utf_error err_code = utf8::internal::validate_next(start, end);
switch (err_code) {
case internal::UTF8_OK :
for (octet_iterator it = sequence_start; it != start; ++it)
*out++ = *it;
break;
case internal::NOT_ENOUGH_ROOM:
throw not_enough_room();
case internal::INVALID_LEAD:
out = utf8::append (replacement, out);
++start;
break;
case internal::INCOMPLETE_SEQUENCE:
case internal::OVERLONG_SEQUENCE:
case internal::INVALID_CODE_POINT:
out = utf8::append (replacement, out);
++start;
// just one replacement mark for the sequence
while (start != end && utf8::internal::is_trail(*start))
++start;
break;
}
}
return out;
}
template <typename octet_iterator, typename output_iterator>
inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out)
{
static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd);
return utf8::replace_invalid(start, end, out, replacement_marker);
}
template <typename octet_iterator>
uint32_t next(octet_iterator& it, octet_iterator end)
{
uint32_t cp = 0;
internal::utf_error err_code = utf8::internal::validate_next(it, end, cp);
switch (err_code) {
case internal::UTF8_OK :
break;
case internal::NOT_ENOUGH_ROOM :
throw not_enough_room();
case internal::INVALID_LEAD :
case internal::INCOMPLETE_SEQUENCE :
case internal::OVERLONG_SEQUENCE :
throw invalid_utf8(*it);
case internal::INVALID_CODE_POINT :
throw invalid_code_point(cp);
}
return cp;
}
template <typename octet_iterator>
uint32_t peek_next(octet_iterator it, octet_iterator end)
{
return utf8::next(it, end);
}
template <typename octet_iterator>
uint32_t prior(octet_iterator& it, octet_iterator start)
{
// can't do much if it == start
if (it == start)
throw not_enough_room();
octet_iterator end = it;
// Go back until we hit either a lead octet or start
while (utf8::internal::is_trail(*(--it)))
if (it == start)
throw invalid_utf8(*it); // error - no lead byte in the sequence
return utf8::peek_next(it, end);
}
/// Deprecated in versions that include "prior"
template <typename octet_iterator>
uint32_t previous(octet_iterator& it, octet_iterator pass_start)
{
octet_iterator end = it;
while (utf8::internal::is_trail(*(--it)))
if (it == pass_start)
throw invalid_utf8(*it); // error - no lead byte in the sequence
octet_iterator temp = it;
return utf8::next(temp, end);
}
template <typename octet_iterator, typename distance_type>
void advance (octet_iterator& it, distance_type n, octet_iterator end)
{
for (distance_type i = 0; i < n; ++i)
utf8::next(it, end);
}
template <typename octet_iterator>
typename std::iterator_traits<octet_iterator>::difference_type
distance (octet_iterator first, octet_iterator last)
{
typename std::iterator_traits<octet_iterator>::difference_type dist;
for (dist = 0; first < last; ++dist)
utf8::next(first, last);
return dist;
}
template <typename u16bit_iterator, typename octet_iterator>
octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result)
{
while (start != end) {
uint32_t cp = utf8::internal::mask16(*start++);
// Take care of surrogate pairs first
if (utf8::internal::is_lead_surrogate(cp)) {
if (start != end) {
uint32_t trail_surrogate = utf8::internal::mask16(*start++);
if (utf8::internal::is_trail_surrogate(trail_surrogate))
cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;
else
throw invalid_utf16(static_cast<uint16_t>(trail_surrogate));
}
else
throw invalid_utf16(static_cast<uint16_t>(cp));
}
// Lone trail surrogate
else if (utf8::internal::is_trail_surrogate(cp))
throw invalid_utf16(static_cast<uint16_t>(cp));
result = utf8::append(cp, result);
}
return result;
}
template <typename u16bit_iterator, typename octet_iterator>
u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)
{
while (start != end) {
uint32_t cp = utf8::next(start, end);
if (cp > 0xffff) { //make a surrogate pair
*result++ = static_cast<uint16_t>((cp >> 10) + internal::LEAD_OFFSET);
*result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);
}
else
*result++ = static_cast<uint16_t>(cp);
}
return result;
}
template <typename octet_iterator, typename u32bit_iterator>
octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result)
{
while (start != end)
result = utf8::append(*(start++), result);
return result;
}
template <typename octet_iterator, typename u32bit_iterator>
u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result)
{
while (start != end)
(*result++) = utf8::next(start, end);
return result;
}
// The iterator class
template <typename octet_iterator>
class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> {
octet_iterator it;
octet_iterator range_start;
octet_iterator range_end;
public:
iterator () {}
explicit iterator (const octet_iterator& octet_it,
const octet_iterator& range_start,
const octet_iterator& range_end) :
it(octet_it), range_start(range_start), range_end(range_end)
{
if (it < range_start || it > range_end)
throw std::out_of_range("Invalid utf-8 iterator position");
}
// the default "big three" are OK
octet_iterator base () const { return it; }
uint32_t operator * () const
{
octet_iterator temp = it;
return utf8::next(temp, range_end);
}
bool operator == (const iterator& rhs) const
{
if (range_start != rhs.range_start || range_end != rhs.range_end)
throw std::logic_error("Comparing utf-8 iterators defined with different ranges");
return (it == rhs.it);
}
bool operator != (const iterator& rhs) const
{
return !(operator == (rhs));
}
iterator& operator ++ ()
{
utf8::next(it, range_end);
return *this;
}
iterator operator ++ (int)
{
iterator temp = *this;
utf8::next(it, range_end);
return temp;
}
iterator& operator -- ()
{
utf8::prior(it, range_start);
return *this;
}
iterator operator -- (int)
{
iterator temp = *this;
utf8::prior(it, range_start);
return temp;
}
}; // class iterator
} // namespace utf8
#endif //header guard

View File

@@ -1,329 +0,0 @@
// Copyright 2006 Nemanja Trifunovic
/*
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
#include <iterator>
namespace utf8
{
// The typedefs for 8-bit, 16-bit and 32-bit unsigned integers
// You may need to change them to match your system.
// These typedefs have the same names as ones from cstdint, or boost/cstdint
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
// Helper code - not intended to be directly called by the library users. May be changed at any time
namespace internal
{
// Unicode constants
// Leading (high) surrogates: 0xd800 - 0xdbff
// Trailing (low) surrogates: 0xdc00 - 0xdfff
const uint16_t LEAD_SURROGATE_MIN = 0xd800u;
const uint16_t LEAD_SURROGATE_MAX = 0xdbffu;
const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u;
const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu;
const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10);
const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN;
// Maximum valid value for a Unicode code point
const uint32_t CODE_POINT_MAX = 0x0010ffffu;
template<typename octet_type>
inline uint8_t mask8(octet_type oc)
{
return static_cast<uint8_t>(0xff & oc);
}
template<typename u16_type>
inline uint16_t mask16(u16_type oc)
{
return static_cast<uint16_t>(0xffff & oc);
}
template<typename octet_type>
inline bool is_trail(octet_type oc)
{
return ((utf8::internal::mask8(oc) >> 6) == 0x2);
}
template <typename u16>
inline bool is_lead_surrogate(u16 cp)
{
return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX);
}
template <typename u16>
inline bool is_trail_surrogate(u16 cp)
{
return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX);
}
template <typename u16>
inline bool is_surrogate(u16 cp)
{
return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX);
}
template <typename u32>
inline bool is_code_point_valid(u32 cp)
{
return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp));
}
template <typename octet_iterator>
inline typename std::iterator_traits<octet_iterator>::difference_type
sequence_length(octet_iterator lead_it)
{
uint8_t lead = utf8::internal::mask8(*lead_it);
if (lead < 0x80)
return 1;
else if ((lead >> 5) == 0x6)
return 2;
else if ((lead >> 4) == 0xe)
return 3;
else if ((lead >> 3) == 0x1e)
return 4;
else
return 0;
}
template <typename octet_difference_type>
inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length)
{
if (cp < 0x80) {
if (length != 1)
return true;
}
else if (cp < 0x800) {
if (length != 2)
return true;
}
else if (cp < 0x10000) {
if (length != 3)
return true;
}
return false;
}
enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT};
/// Helper for get_sequence_x
template <typename octet_iterator>
utf_error increase_safely(octet_iterator& it, octet_iterator end)
{
if (++it == end)
return NOT_ENOUGH_ROOM;
if (!utf8::internal::is_trail(*it))
return INCOMPLETE_SEQUENCE;
return UTF8_OK;
}
#define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;}
/// get_sequence_x functions decode utf-8 sequences of the length x
template <typename octet_iterator>
utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point)
{
if (it == end)
return NOT_ENOUGH_ROOM;
code_point = utf8::internal::mask8(*it);
return UTF8_OK;
}
template <typename octet_iterator>
utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point)
{
if (it == end)
return NOT_ENOUGH_ROOM;
code_point = utf8::internal::mask8(*it);
UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f);
return UTF8_OK;
}
template <typename octet_iterator>
utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point)
{
if (it == end)
return NOT_ENOUGH_ROOM;
code_point = utf8::internal::mask8(*it);
UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff);
UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
code_point += (*it) & 0x3f;
return UTF8_OK;
}
template <typename octet_iterator>
utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point)
{
if (it == end)
return NOT_ENOUGH_ROOM;
code_point = utf8::internal::mask8(*it);
UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff);
UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
code_point += (utf8::internal::mask8(*it) << 6) & 0xfff;
UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)
code_point += (*it) & 0x3f;
return UTF8_OK;
}
#undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR
template <typename octet_iterator>
utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point)
{
// Save the original value of it so we can go back in case of failure
// Of course, it does not make much sense with i.e. stream iterators
octet_iterator original_it = it;
uint32_t cp = 0;
// Determine the sequence length based on the lead octet
typedef typename std::iterator_traits<octet_iterator>::difference_type octet_difference_type;
const octet_difference_type length = utf8::internal::sequence_length(it);
// Get trail octets and calculate the code point
utf_error err = UTF8_OK;
switch (length) {
case 0:
return INVALID_LEAD;
case 1:
err = utf8::internal::get_sequence_1(it, end, cp);
break;
case 2:
err = utf8::internal::get_sequence_2(it, end, cp);
break;
case 3:
err = utf8::internal::get_sequence_3(it, end, cp);
break;
case 4:
err = utf8::internal::get_sequence_4(it, end, cp);
break;
}
if (err == UTF8_OK) {
// Decoding succeeded. Now, security checks...
if (utf8::internal::is_code_point_valid(cp)) {
if (!utf8::internal::is_overlong_sequence(cp, length)){
// Passed! Return here.
code_point = cp;
++it;
return UTF8_OK;
}
else
err = OVERLONG_SEQUENCE;
}
else
err = INVALID_CODE_POINT;
}
// Failure branch - restore the original value of the iterator
it = original_it;
return err;
}
template <typename octet_iterator>
inline utf_error validate_next(octet_iterator& it, octet_iterator end) {
uint32_t ignored;
return utf8::internal::validate_next(it, end, ignored);
}
} // namespace internal
/// The library API - functions intended to be called by the users
// Byte order mark
const uint8_t bom[] = {0xef, 0xbb, 0xbf};
template <typename octet_iterator>
octet_iterator find_invalid(octet_iterator start, octet_iterator end)
{
octet_iterator result = start;
while (result != end) {
utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end);
if (err_code != internal::UTF8_OK)
return result;
}
return result;
}
template <typename octet_iterator>
inline bool is_valid(octet_iterator start, octet_iterator end)
{
return (utf8::find_invalid(start, end) == end);
}
template <typename octet_iterator>
inline bool starts_with_bom (octet_iterator it, octet_iterator end)
{
return (
((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) &&
((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) &&
((it != end) && (utf8::internal::mask8(*it)) == bom[2])
);
}
//Deprecated in release 2.3
template <typename octet_iterator>
inline bool is_bom (octet_iterator it)
{
return (
(utf8::internal::mask8(*it++)) == bom[0] &&
(utf8::internal::mask8(*it++)) == bom[1] &&
(utf8::internal::mask8(*it)) == bom[2]
);
}
} // namespace utf8
#endif // header guard

View File

@@ -1,228 +0,0 @@
// Copyright 2006 Nemanja Trifunovic
/*
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731
#include "core.h"
namespace utf8
{
namespace unchecked
{
template <typename octet_iterator>
octet_iterator append(uint32_t cp, octet_iterator result)
{
if (cp < 0x80) // one octet
*(result++) = static_cast<uint8_t>(cp);
else if (cp < 0x800) { // two octets
*(result++) = static_cast<uint8_t>((cp >> 6) | 0xc0);
*(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80);
}
else if (cp < 0x10000) { // three octets
*(result++) = static_cast<uint8_t>((cp >> 12) | 0xe0);
*(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);
*(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80);
}
else { // four octets
*(result++) = static_cast<uint8_t>((cp >> 18) | 0xf0);
*(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f)| 0x80);
*(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);
*(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80);
}
return result;
}
template <typename octet_iterator>
uint32_t next(octet_iterator& it)
{
uint32_t cp = utf8::internal::mask8(*it);
typename std::iterator_traits<octet_iterator>::difference_type length = utf8::internal::sequence_length(it);
switch (length) {
case 1:
break;
case 2:
it++;
cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f);
break;
case 3:
++it;
cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff);
++it;
cp += (*it) & 0x3f;
break;
case 4:
++it;
cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff);
++it;
cp += (utf8::internal::mask8(*it) << 6) & 0xfff;
++it;
cp += (*it) & 0x3f;
break;
}
++it;
return cp;
}
template <typename octet_iterator>
uint32_t peek_next(octet_iterator it)
{
return utf8::unchecked::next(it);
}
template <typename octet_iterator>
uint32_t prior(octet_iterator& it)
{
while (utf8::internal::is_trail(*(--it))) ;
octet_iterator temp = it;
return utf8::unchecked::next(temp);
}
// Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous)
template <typename octet_iterator>
inline uint32_t previous(octet_iterator& it)
{
return utf8::unchecked::prior(it);
}
template <typename octet_iterator, typename distance_type>
void advance (octet_iterator& it, distance_type n)
{
for (distance_type i = 0; i < n; ++i)
utf8::unchecked::next(it);
}
template <typename octet_iterator>
typename std::iterator_traits<octet_iterator>::difference_type
distance (octet_iterator first, octet_iterator last)
{
typename std::iterator_traits<octet_iterator>::difference_type dist;
for (dist = 0; first < last; ++dist)
utf8::unchecked::next(first);
return dist;
}
template <typename u16bit_iterator, typename octet_iterator>
octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result)
{
while (start != end) {
uint32_t cp = utf8::internal::mask16(*start++);
// Take care of surrogate pairs first
if (utf8::internal::is_lead_surrogate(cp)) {
uint32_t trail_surrogate = utf8::internal::mask16(*start++);
cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;
}
result = utf8::unchecked::append(cp, result);
}
return result;
}
template <typename u16bit_iterator, typename octet_iterator>
u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)
{
while (start < end) {
uint32_t cp = utf8::unchecked::next(start);
if (cp > 0xffff) { //make a surrogate pair
*result++ = static_cast<uint16_t>((cp >> 10) + internal::LEAD_OFFSET);
*result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);
}
else
*result++ = static_cast<uint16_t>(cp);
}
return result;
}
template <typename octet_iterator, typename u32bit_iterator>
octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result)
{
while (start != end)
result = utf8::unchecked::append(*(start++), result);
return result;
}
template <typename octet_iterator, typename u32bit_iterator>
u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result)
{
while (start < end)
(*result++) = utf8::unchecked::next(start);
return result;
}
// The iterator class
template <typename octet_iterator>
class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> {
octet_iterator it;
public:
iterator () {}
explicit iterator (const octet_iterator& octet_it): it(octet_it) {}
// the default "big three" are OK
octet_iterator base () const { return it; }
uint32_t operator * () const
{
octet_iterator temp = it;
return utf8::unchecked::next(temp);
}
bool operator == (const iterator& rhs) const
{
return (it == rhs.it);
}
bool operator != (const iterator& rhs) const
{
return !(operator == (rhs));
}
iterator& operator ++ ()
{
::std::advance(it, utf8::internal::sequence_length(it));
return *this;
}
iterator operator ++ (int)
{
iterator temp = *this;
::std::advance(it, utf8::internal::sequence_length(it));
return temp;
}
iterator& operator -- ()
{
utf8::unchecked::prior(it);
return *this;
}
iterator operator -- (int)
{
iterator temp = *this;
utf8::unchecked::prior(it);
return temp;
}
}; // class iterator
} // namespace utf8::unchecked
} // namespace utf8
#endif // header guard

View File

@@ -7,6 +7,9 @@ if(COMMAND cmake_policy)
if(POLICY CMP0042)
cmake_policy(SET CMP0042 NEW)
endif()
if(POLICY CMP0067)
cmake_policy(SET CMP0067 NEW)
endif()
endif()
@@ -49,7 +52,7 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7")
# add a dependency on the versino file
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS version)
set(FIND_LIBRARY_USE_LIB64_PATHS ON)
set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS TRUE)
# use simgear version also as the SO version (if building SOs)
SET(SIMGEAR_SOVERSION ${SIMGEAR_VERSION})
@@ -135,6 +138,16 @@ include (DetectArch)
# keep the compatability link option as the default
option(OSG_FSTREAM_EXPORT_FIXED "Set to ON if the osgDB fstream export patch is applied" OFF)
if (CMAKE_COMPILER_IS_GNUCXX OR CLANG)
if (CMAKE_VERSION VERSION_LESS 3.1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++${CMAKE_CXX_STANDARD}")
elseif (CMAKE_VERSION VERSION_LESS 3.8)
# policy CMP0067 (try_compile does not honor CMAKE_CXX_STANDARD)
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++${CMAKE_CXX_STANDARD}")
endif()
endif()
if (MSVC)
GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_BINARY_DIR} PATH)
if (CMAKE_CL_64)
@@ -249,8 +262,8 @@ else()
# declaring symbols as declspec(import)
add_definitions(-DHAVE_EXPAT_CONFIG_H -DXML_STATIC)
set(EXPAT_INCLUDE_DIRS
${PROJECT_SOURCE_DIR}/3rdparty/expat
${PROJECT_BINARY_DIR}/3rdparty/expat)
${PROJECT_SOURCE_DIR}/3rdparty/expat
${PROJECT_BINARY_DIR}/3rdparty/expat)
endif(SYSTEM_EXPAT)
check_include_file(inttypes.h HAVE_INTTYPES_H)
@@ -264,9 +277,17 @@ if(HAVE_INTTYPES_H)
endif()
if(ENABLE_RTI)
# See if we have any rti library variant installed
message(STATUS "RTI: ENABLED")
find_package(RTI)
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
SET(ENV{PKG_CONFIG_PATH} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
pkg_check_modules(RTI hla-rti13)
endif(PKG_CONFIG_FOUND)
if(RTI_FOUND)
SET(RTI_INCLUDE_DIR "${RTI_INCLUDE_DIRS}")
message(STATUS "RTI: ENABLED")
else()
message(STATUS "RTI: DISABLED")
endif(RTI_FOUND)
else()
message(STATUS "RTI: DISABLED")
endif(ENABLE_RTI)
@@ -274,7 +295,7 @@ endif(ENABLE_RTI)
if(ENABLE_GDAL)
find_package(GDAL 2.0.0 REQUIRED)
if (GDAL_FOUND)
include_directories(${GDAL_INCLUDE_DIR})
include_directories(${GDAL_INCLUDE_DIR})
endif(GDAL_FOUND)
endif(ENABLE_GDAL)
@@ -335,7 +356,7 @@ SET(CMAKE_MINSIZEREL_POSTFIX "" CACHE STRING "add a postfix, usually empty on wi
# isnan might not be real symbol, so can't check using function_exists
check_cxx_source_compiles(
"#include <cstdlib>
"#include <cmath>
int main() { return std::isnan(0.0);} "
HAVE_STD_ISNAN)
@@ -343,14 +364,35 @@ if (NOT ${HAVE_STD_ISNAN})
message(FATAL_ERROR "Your compiler lacks C++11 std::isnan, please update it")
endif()
# Check if the <regex> implementation in the C++ standard library is usable.
# This is necessary because g++ 4.8 lies about its C++11 compliance: its
# <regex> is utterly unusable, cf. [1].
# The big preprocessor test essentially comes from [2], and gcc upstream devs
# appear to back it (see comments following [2], as well as [3]).
#
# [1] https://stackoverflow.com/a/12665408/4756009
# [2] https://stackoverflow.com/a/41186162/4756009
# [3] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78905
check_cxx_source_compiles(
"#include <regex>
int main() {
#if __cplusplus >= 201103L && \
(!defined(__GLIBCXX__) || \
(__cplusplus >= 201402L) || \
defined(_GLIBCXX_REGEX_DFS_QUANTIFIERS_LIMIT) || \
defined(_GLIBCXX_REGEX_STATE_LIMIT) || \
(defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE > 4))
#else
nullptr = void; // intentionally trigger a compilation error
#endif
}"
HAVE_WORKING_STD_REGEX)
if(CMAKE_COMPILER_IS_GNUCXX)
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")
endif()
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.4)
message(WARNING "GCC 4.4 will be required soon, please upgrade")
endif()
@@ -378,10 +420,6 @@ if (CLANG)
# fix Boost compilation :(
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
if (CMAKE_VERSION VERSION_LESS 3.1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endif()
if(ENABLE_SIMD)
if (X86 OR X86_64)
set(CMAKE_C_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse")
@@ -454,6 +492,8 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS_C} ${MSVC_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS_CXX} ${MSVC_FLAGS} ${BOOST_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MSVC_LD_FLAGS}")
include(CheckCXXFeatures)
# use BEFORE to ensure local directories are used first,
# ahead of system-installed libs
include_directories(BEFORE ${PROJECT_BINARY_DIR}/simgear)

View File

@@ -0,0 +1,30 @@
check_cxx_source_compiles("
#include <utility>
#include <type_traits>
std::make_index_sequence<0> t;
int main() {}" HAVE_STD_INDEX_SEQUENCE
)
check_cxx_source_compiles("
#include <type_traits>
std::remove_cv_t<const int> t;
int main() {}" HAVE_STD_REMOVE_CV_T
)
check_cxx_source_compiles("
#include <type_traits>
std::remove_cvref_t<const int&> t;
int main() {}" HAVE_STD_REMOVE_CVREF_T
)
check_cxx_source_compiles("
#include <type_traits>
std::enable_if_t<true, int> t;
int main() {}" HAVE_STD_ENABLE_IF_T
)
check_cxx_source_compiles("
#include <type_traits>
std::bool_constant<true> t;
int main() {}" HAVE_STD_BOOL_CONSTANT
)

View File

@@ -10,12 +10,13 @@
#
# Created by Erik Hofman.
FIND_PATH(AAX_INCLUDE_DIR aax/aeonwave.hpp
FIND_PATH(AAX_INCLUDE_DIR aax/aax.h
HINTS
$ENV{AAXDIR}
$ENV{ProgramFiles}/aax
$ENV{ProgramFiles}/AeonWave
$ENV{ProgramFiles}/Adalin/AeonWave
${CMAKE_SOURCE_DIR}/aax
PATH_SUFFIXES include
PATHS
~/Library/Frameworks
@@ -26,23 +27,35 @@ FIND_PATH(AAX_INCLUDE_DIR aax/aeonwave.hpp
)
FIND_LIBRARY(AAX_LIBRARY
NAMES AAX aax AAX32 libAAX32
NAMES AAX aax AAX32
HINTS
$ENV{AAXDIR}
$ENV{ProgramFiles}/AAX
$ENV{ProgramFiles}/AeonWave
$ENV{ProgramFiles}/Adalin/AeonWave
${CMAKE_BUILD_DIR}/aax
PATH_SUFFIXES bin lib lib/${CMAKE_LIBRARY_ARCHITECTURE} lib64 libs64 libs libs/Win32 libs/Win64
PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local
/usr
/opt
/usr/local
)
SET(AAX_FOUND "NO")
IF(AAX_LIBRARY AND AAX_INCLUDE_DIR)
SET(AAX_FOUND "YES")
ELSE(AAX_LIBRARY AND AAX_INCLUDE_DIR)
IF(NOT AAX_INCLUDE_DIR)
MESSAGE(FATAL_ERROR "Unable to find the AAX library development files.")
SET(AAX_FOUND "NO")
ENDIF(NOT AAX_INCLUDE_DIR)
IF(NOT AAX_LIBRARY)
IF(SINGLE_PACKAGE)
SET(AAX_LIBRARY "${aax_BUILD_DIR}/aax/AAX32.dll")
SET(AAX_FOUND "YES")
ELSE(SINGLE_PACKAGE)
ENDIF(SINGLE_PACKAGE)
ENDIF(NOT AAX_LIBRARY)
ENDIF(AAX_LIBRARY AND AAX_INCLUDE_DIR)

View File

@@ -2,6 +2,10 @@
documentation. It has a .cxx extension so that emacs will happily
autoindent correctly. */
/**
* \namespace simgear
* \brief \ref index "SimGear" main namespace.
*/
/** \mainpage SimGear
* Simulation, Visualization, and Game development libraries.

View File

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

View File

@@ -7,6 +7,7 @@ find_dependency(Threads)
set(SIMGEAR_HEADLESS @SIMGEAR_HEADLESS@)
set(SIMGEAR_SOUND @ENABLE_SOUND@)
set(USE_AEONWAVE @USE_AEONWAVE@)
# OpenAL isn't a public dependency, so maybe not needed
#if (SIMGEAR_SOUND)

View File

@@ -1,13 +0,0 @@
Building a SimGear RPM package for Red Hat
Please see the "package/openSUSE" directory for an
example how to build a SimGear RPM package with
shared SimGear libraries.
You may need to adapt the names (exact spelling) of some
of the package dependencies in the openSUSE RPM spec,
since these may slightly differ for Red Hat.
(If you have a working and tested Red Hat RPM spec,
you're welcome to contribute it to this project.)

View File

@@ -1,23 +0,0 @@
Building a SimGear RPM package for openSUSE
(Last tested with openSUSE 11.4+12.1)
This directory contains the files which, along with
the source code tar files, can be used to build
an RPM package targeted at an openSUSE Linux system.
To build SimGear from source do the following:
1. obtain simgear-2.8.0.tar.bz2 (adapt version if
necessary) and copy it into ~/rpmbuild/SOURCES
2. look in the BuildRequires section of SimGear.spec
and check that all the packages referred to are
installed (note, some of these packages may be part
of openSUSE's "games" repository).
3. run 'rpmbuild -ba simgear.spec' and find the RPM
build result in ~/rpmbuild/RPMS
That's all!

View File

@@ -1,63 +0,0 @@
Summary: Simulator Construction Gear
Name: SimGear
Version: 2.8.0
Release: 1
License: LGPL
URL: http://www.flightgear.org
Group: Amusements/Games/3D/Simulation
Source: http://mirrors.ibiblio.org/pub/mirrors/flightgear/ftp/Source/simgear-%{version}.tar.bz2
BuildRoot: %{_tmppath}/%{name}-%{version}-build
BuildRequires: gcc, gcc-c++, cmake
BuildRequires: libopenal1-soft, openal-soft
BuildRequires: libOpenSceneGraph-devel >= 3.0
BuildRequires: zlib, zlib-devel
BuildRequires: libjpeg62, libjpeg62-devel
BuildRequires: boost-devel >= 1.37
BuildRequires: subversion-devel, libapr1-devel
Requires: OpenSceneGraph-plugins >= 3.0
%description
This package contains a tools and libraries useful for constructing
simulation and visualization applications such as FlightGear or TerraGear.
%package devel
Group: Development/Libraries/Other
Summary: Development header files for SimGear
Requires: SimGear = %{version}
%description devel
Development headers and libraries for building applications against SimGear.
%prep
%setup -T -q -n simgear-%{version} -b 0
%build
export CFLAGS="$RPM_OPT_FLAGS"
export CXXFLAGS="$RPM_OPT_FLAGS"
# build SHARED simgear libraries
cmake -DCMAKE_INSTALL_PREFIX=%{_prefix} -DSIMGEAR_SHARED:BOOL=ON -DENABLE_TESTS:BOOL=OFF -DJPEG_FACTORY:BOOL=ON
make %{?_smp_mflags}
%install
make DESTDIR=%{buildroot} install
%post -p /sbin/ldconfig
%postun -p /sbin/ldconfig
%files
%defattr (-, root, root, -)
%doc AUTHORS COPYING ChangeLog NEWS README
%{_libdir}/libSimGear*.so.*
%files devel
%defattr(-,root,root,-)
%dir %{_includedir}/simgear
%{_includedir}/simgear/*
%{_libdir}/libSimGear*.so
%changelog
* Mon Jul 02 2012 thorstenb@flightgear.org
- Initial version

View File

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

View File

@@ -53,7 +53,7 @@ void testBasic()
SG_CHECK_EQUAL(b1.gen_index(), 3040320);
SG_CHECK_EQUAL(b1.gen_base_path(), "e000n50/e005n55");
SG_VERIFY(b1.isValid());
SGBucket b2(-10.1, -43.8);
SG_CHECK_EQUAL(b2.get_chunk_lon(), -11);
SG_CHECK_EQUAL(b2.get_chunk_lat(), -44);
@@ -62,7 +62,7 @@ void testBasic()
SG_CHECK_EQUAL(b2.get_y(), 1);
SG_CHECK_EQUAL(b2.gen_base_path(), "w020s50/w011s44");
SG_VERIFY(b2.isValid());
SGBucket b3(123.48, 9.01);
SG_CHECK_EQUAL(b3.get_chunk_lon(), 123);
SG_CHECK_EQUAL(b3.get_chunk_lat(), 9);
@@ -70,10 +70,10 @@ void testBasic()
SG_CHECK_EQUAL(b3.get_y(), 0);
SG_CHECK_EQUAL(b3.gen_base_path(), "e120n00/e123n09");
SG_VERIFY(b3.isValid());
SGBucket defBuck;
SG_VERIFY(!defBuck.isValid());
b3.make_bad();
SG_VERIFY(!b3.isValid());
@@ -87,19 +87,47 @@ void testBasic()
SG_CHECK_EQUAL(atAntiMeridian2.get_chunk_lon(), -180);
SG_CHECK_EQUAL(atAntiMeridian2.get_x(), 0);
// check comparisom operator overload
// check comparison operator overload
SGBucket b4(5.11, 55.1);
SG_VERIFY(b1 == b4); // should be equal
SG_VERIFY(b1 == b1);
SG_VERIFY(b1 != defBuck);
SG_VERIFY(b1 != b2);
// check wrapping/clipping of inputs
// check wrapping/clipping of inputs
SGBucket wrapMeridian(-200.0, 45.0);
SG_CHECK_EQUAL(wrapMeridian.get_chunk_lon(), 160);
SGBucket clipPole(48.9, 91);
SG_CHECK_EQUAL(clipPole.get_chunk_lat(), 89);
// test override of a bucket's geod
auto geod = SGGeod::fromDegFt(-86.678, 36.1248, 599.0);
#ifndef NO_DEPRECATED_API
SGBucket bna_airport;
bna_airport.set_bucket(geod);
#else
SGBucket bna_airport(geod);
#endif
SG_VERIFY(bna_airport.isValid());
SG_CHECK_EQUAL(bna_airport.get_chunk_lon(), -87); // left origin of the 1-degree chunk
SG_CHECK_EQUAL(bna_airport.get_chunk_lat(), 36); // bottom origin of the 1-degree chunk
SG_CHECK_EQUAL(bna_airport.get_x(), 1); // buckets are 0.25 deg wide at the W87 parallel
// we're 0.322 deg from the origin (second bucket)
SG_CHECK_EQUAL(bna_airport.get_y(), 0); // buckets are always 0.125 deg tall
// we're 0.1248 deg from the origin (first bucket)
SG_CHECK_EQUAL(bna_airport.gen_base_path(), "w090n30/w087n36");
SG_CHECK_EQUAL_EP2(bna_airport.get_width_m(), 22479.1, 0.1);
SG_CHECK_EQUAL_EP2(bna_airport.get_height_m(), 13914.9, 0.1);
SG_CHECK_EQUAL(bna_airport.gen_index_str(), "1531777"); // 0x175F81 = b01011101|01111110|000|001
// = 93-180 | 126-90 | 0 | 1
// = -87 | 36 | 0 | 1
// test stream output
cout << "[TEST] BNA Airport: " << bna_airport << endl;
auto center = bna_airport.get_center();
cout << "[TEST] BNA lon: " << center.getLongitudeDeg() << endl;
cout << "[TEST] BNA lat: " << center.getLatitudeDeg() << endl;
}
void testPolar()
@@ -110,17 +138,17 @@ void testPolar()
SG_CHECK_EQUAL(b1.get_chunk_lon(), 0);
SG_CHECK_EQUAL(b1.get_x(), 0);
SG_CHECK_EQUAL(b1.get_y(), 7);
SG_CHECK_EQUAL_EP(b1.get_highest_lat(), 90.0);
SG_CHECK_EQUAL_EP(b1.get_width_m(), 10.0);
SG_CHECK_EQUAL(b2.get_chunk_lat(), 89);
SG_CHECK_EQUAL(b2.get_chunk_lon(), 0);
SG_CHECK_EQUAL(b2.get_x(), 0);
SG_CHECK_EQUAL(b2.get_y(), 7);
SG_CHECK_EQUAL(b1.gen_index(), b2.gen_index());
SGGeod actualNorthPole1 = b1.get_corner(2);
SGGeod actualNorthPole2 = b1.get_corner(3);
SG_CHECK_EQUAL_EP(actualNorthPole1.getLatitudeDeg(), 90.0);
@@ -131,11 +159,11 @@ void testPolar()
SGBucket b3(-2, 89.88);
SGBucket b4(-7, 89.88);
SG_CHECK_EQUAL(b3.gen_index(), b4.gen_index());
// south pole
SGBucket b5(-170, -89.88);
SGBucket b6(-179, -89.88);
SG_CHECK_EQUAL(b5.get_chunk_lat(), -90);
SG_CHECK_EQUAL(b5.get_chunk_lon(), -180);
SG_CHECK_EQUAL(b5.get_x(), 0);
@@ -143,17 +171,16 @@ void testPolar()
SG_CHECK_EQUAL(b5.gen_index(), b6.gen_index());
SG_CHECK_EQUAL_EP(b5.get_highest_lat(), -90.0);
SG_CHECK_EQUAL_EP(b5.get_width_m(), 10.0);
SGGeod actualSouthPole1 = b5.get_corner(0);
SGGeod actualSouthPole2 = b5.get_corner(1);
SG_CHECK_EQUAL_EP(actualSouthPole1.getLatitudeDeg(), -90.0);
SG_CHECK_EQUAL_EP(actualSouthPole1.getLongitudeDeg(), -180);
SG_CHECK_EQUAL_EP(actualSouthPole2.getLatitudeDeg(), -90.0);
SG_CHECK_EQUAL_EP(actualSouthPole2.getLongitudeDeg(), -168);
SGBucket b7(200, 89.88);
SG_CHECK_EQUAL(b7.get_chunk_lon(), -168);
}
// test the tiles just below the pole (between 86 & 89 degrees N/S)
@@ -167,7 +194,7 @@ void testNearPolar()
SGBucket b3(176.1, 88.5);
SG_CHECK_EQUAL(b3.get_chunk_lon(), 176);
SGBucket b4(-178, 88.5);
SG_CHECK_EQUAL(b4.get_chunk_lon(), -180);
}
@@ -181,7 +208,7 @@ void testOffset()
SG_CHECK_EQUAL(b1.get_chunk_lon(), -60);
SG_CHECK_EQUAL(b1.get_x(), 1);
SG_CHECK_EQUAL(b1.get_y(), 7);
// offset vertically
SGBucket b2(b1.sibling(0, 1));
SG_CHECK_EQUAL(b2.get_chunk_lat(), 22);
@@ -190,7 +217,7 @@ void testOffset()
SG_CHECK_EQUAL(b2.get_y(), 0);
SG_CHECK_EQUAL(b2.gen_index(), sgBucketOffset(-59.8, 21.9, 0, 1));
// offset vertically and horizontally. We compute horizontal (x)
// movement at the target latitude, so this should move 0.25 * -3 degrees,
// NOT 0.125 * -3 degrees.
@@ -199,7 +226,7 @@ void testOffset()
SG_CHECK_EQUAL(b3.get_chunk_lon(), -61);
SG_CHECK_EQUAL(b3.get_x(), 1);
SG_CHECK_EQUAL(b3.get_y(), 0);
SG_CHECK_EQUAL(b3.gen_index(), sgBucketOffset(-59.8, 21.9, -3, 1));
}
@@ -210,40 +237,40 @@ void testPolarOffset()
SG_CHECK_EQUAL(b1.get_chunk_lon(), -12);
SG_CHECK_EQUAL(b1.get_x(), 0);
SG_CHECK_EQUAL(b1.get_y(), 3);
// offset horizontally
SGBucket b2(b1.sibling(-2, 0));
SG_CHECK_EQUAL(b2.get_chunk_lat(), -90);
SG_CHECK_EQUAL(b2.get_chunk_lon(), -36);
SG_CHECK_EQUAL(b2.get_x(), 0);
SG_CHECK_EQUAL(b2.get_y(), 3);
SG_CHECK_EQUAL(b2.gen_index(), sgBucketOffset(-11.7, -89.6, -2, 0));
// offset and wrap
// offset and wrap
SGBucket b3(-170, 89.1);
SGBucket b4(b3.sibling(-1, 0));
SG_CHECK_EQUAL(b4.get_chunk_lat(), 89);
SG_CHECK_EQUAL(b4.get_chunk_lon(), 168);
SG_CHECK_EQUAL(b4.get_x(), 0);
SG_CHECK_EQUAL(b4.get_y(), 0);
SG_CHECK_EQUAL(b4.gen_index(), sgBucketOffset(-170, 89.1, -1, 0));
SGBucket b5(177, 87.3);
SGBucket b6(b5.sibling(1, 1));
SG_CHECK_EQUAL(b6.get_chunk_lat(), 87);
SG_CHECK_EQUAL(b6.get_chunk_lon(), -180);
SG_CHECK_EQUAL(b6.get_x(), 0);
SG_CHECK_EQUAL(b6.get_y(), 3);
SG_CHECK_EQUAL(b6.gen_index(), sgBucketOffset(177, 87.3, 1, 1));
// offset vertically towards the pole
SGBucket b7(b1.sibling(0, -5));
SG_VERIFY(!b7.isValid());
SG_VERIFY(!SGBucket(0, 90).sibling(0, 1).isValid());
}
@@ -256,29 +283,80 @@ void testOffsetWrap()
SG_CHECK_EQUAL(b1.get_chunk_lon(), -180);
SG_CHECK_EQUAL(b1.get_x(), 1);
SG_CHECK_EQUAL(b1.get_y(), 6);
SGBucket b2(b1.sibling(-2, 0));
SG_CHECK_EQUAL(b2.get_chunk_lat(), 16);
SG_CHECK_EQUAL(b2.get_chunk_lon(), 179);
SG_CHECK_EQUAL(b2.get_x(), 7);
SG_CHECK_EQUAL(b2.get_y(), 6);
SG_CHECK_EQUAL(b2.gen_index(), sgBucketOffset(-179.8, 16.8, -2, 0));
}
void testSiblings()
{
SGBucket bna_airport(-86.678, 36.1248);
SG_VERIFY(bna_airport.isValid());
// retrieve the sibling two positions north-east of my position
auto sib1 = bna_airport.sibling(2, 2);
SG_CHECK_EQUAL(sib1.get_chunk_lon(), bna_airport.get_chunk_lon());
SG_CHECK_EQUAL(sib1.get_chunk_lat(), bna_airport.get_chunk_lat());
SG_CHECK_EQUAL(sib1.get_x(), 3); // my x-pos (1) + 2 = 3
SG_CHECK_EQUAL(sib1.get_y(), 2); // my y-pos (0) + 2 = 2
SG_CHECK_EQUAL(sib1.gen_base_path(), bna_airport.gen_base_path());
// retrieve the one sibling two positions to the north-east
std::vector<SGBucket> siblings;
bna_airport.siblings(2, 2, siblings);
SG_CHECK_EQUAL(siblings.size(), static_cast<std::vector<SGBucket>::size_type>(1));
siblings.clear();
// retrieve the one sibling at the chunk origin of sib1
sib1.siblings(-2, -2, siblings);
SG_CHECK_EQUAL(siblings.size(), static_cast<std::vector<SGBucket>::size_type>(1));
siblings.clear();
// calculate delta between two buckets
int dx = 0;
int dy = 0;
sgBucketDiff(bna_airport, sib1, &dx, &dy);
SG_CHECK_EQUAL(dx, 2);
SG_CHECK_EQUAL(dy, 2);
// retrieve all siblings between two geodetic locations
auto geod_bna = SGGeod::fromDegFt(-86.678, 36.1248, 599.0);
auto geod_m54 = SGGeod::fromDegFt(-86.317, 36.1908, 122.0);
sgGetBuckets(geod_bna, geod_m54, siblings);
SG_CHECK_EQUAL(siblings.size(), static_cast<std::vector<SGBucket>::size_type>(4));
siblings.clear();
// edge cases
// ensure you cannot retrieve the sibling of an invalid bucket
SGBucket bad;
auto bad_sib = bad.sibling(1, 1);
SG_CHECK_EQUAL(bad_sib.get_chunk_lon(), -1000);
SG_CHECK_EQUAL(bad.siblings(2, 2, siblings), 0);
// if we drop below the 22nd parallel, the bucket widths are half the size
// expect this to retrieve two buckets
bna_airport.siblings(0, -160, siblings);
SG_CHECK_EQUAL(siblings.size(), static_cast<std::vector<SGBucket>::size_type>(2));
siblings.clear();
}
int main(int argc, char* argv[])
{
testBucketSpans();
testBasic();
testPolar();
testNearPolar();
testOffset();
testOffsetWrap();
testPolarOffset();
testSiblings();
cout << "all tests passed OK" << endl;
return 0; // passed
}

View File

@@ -22,8 +22,10 @@
#include "CanvasEventManager.hxx"
#include "CanvasEventVisitor.hxx"
#include "CanvasPlacement.hxx"
#include <simgear/canvas/events/KeyboardEvent.hxx>
#include <simgear/canvas/events/MouseEvent.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/scene/util/parse_color.hxx>
#include <simgear/scene/util/RenderConstants.hxx>
@@ -32,9 +34,6 @@
#include <osgText/Text>
#include <osgViewer/Viewer>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/foreach.hpp>
namespace simgear
{
namespace canvas
@@ -64,18 +63,9 @@ namespace canvas
//----------------------------------------------------------------------------
Canvas::Canvas(SGPropertyNode* node):
PropertyBasedElement(node),
_canvas_mgr(0),
_event_manager(new EventManager),
_size_x(-1),
_size_y(-1),
_view_width(-1),
_view_height(-1),
_status(node, "status"),
_status_msg(node, "status-msg"),
_sampling_dirty(false),
_render_dirty(true),
_visible(true),
_render_always(false)
_status_msg(node, "status-msg")
{
_status = 0;
setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y);
@@ -236,6 +226,10 @@ namespace canvas
if( _status & STATUS_DIRTY )
{
// Retrieve reference here, to ensure the scene group is not deleted while
// creating the new texture and camera
osg::ref_ptr<osg::Group> root_scene_group = _root_group->getSceneGroup();
_texture.setSize(_size_x, _size_y);
if( !_texture.serviceable() )
@@ -261,7 +255,7 @@ namespace canvas
parseColor(_node->getStringValue("background"), clear_color);
camera->setClearColor(clear_color);
camera->addChild(_root_group->getMatrixTransform());
camera->addChild(root_scene_group);
if( _texture.serviceable() )
{
@@ -281,7 +275,7 @@ namespace canvas
if( _visible || _render_always )
{
BOOST_FOREACH(CanvasWeakPtr canvas_weak, _child_canvases)
for(auto& canvas_weak: _child_canvases)
{
// TODO should we check if the image the child canvas is displayed
// within is really visible?
@@ -293,7 +287,7 @@ namespace canvas
if( _render_dirty )
{
// Also mark all canvases this canvas is displayed within as dirty
BOOST_FOREACH(CanvasWeakPtr canvas_weak, _parent_canvases)
for(auto& canvas_weak: _parent_canvases)
{
CanvasPtr canvas = canvas_weak.lock();
if( canvas )
@@ -522,8 +516,8 @@ namespace canvas
{
const std::string& name = node->getNameString();
if( boost::starts_with(name, "status")
|| boost::starts_with(name, "data-") )
if( strutils::starts_with(name, "status")
|| strutils::starts_with(name, "data-") )
return;
_render_dirty = true;
@@ -538,7 +532,7 @@ namespace canvas
if( !placements.empty() )
{
bool placement_dirty = false;
BOOST_FOREACH(PlacementPtr& placement, placements)
for(auto& placement: placements)
{
// check if change can be directly handled by placement
if( placement->getProps() == node->getParent()

View File

@@ -33,7 +33,7 @@
#include <osg/NodeCallback>
#include <osg/observer_ptr>
#include <boost/scoped_ptr.hpp>
#include <memory>
#include <string>
namespace simgear
@@ -211,21 +211,21 @@ namespace canvas
protected:
CanvasMgr *_canvas_mgr;
CanvasMgr *_canvas_mgr {nullptr};
boost::scoped_ptr<EventManager> _event_manager;
std::unique_ptr<EventManager> _event_manager;
int _size_x,
_size_y,
_view_width,
_view_height;
int _size_x {-1},
_size_y {-1},
_view_width {-1},
_view_height {-1};
PropertyObject<int> _status;
PropertyObject<std::string> _status_msg;
bool _sampling_dirty,
_render_dirty,
_visible;
bool _sampling_dirty {false},
_render_dirty {true},
_visible {true};
ODGauge _texture;
@@ -235,7 +235,9 @@ namespace canvas
ElementWeakPtr _focus_element;
CullCallbackPtr _cull_callback;
bool _render_always; //!< Used to disable automatic lazy rendering (culling)
/** Used to disable automatic lazy rendering (culling) */
bool _render_always {false};
std::vector<SGPropertyNode*> _dirty_placements;
std::vector<Placements> _placements;

View File

@@ -22,13 +22,11 @@
#include "CanvasWindow.hxx"
#include <simgear/canvas/Canvas.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <osgGA/GUIEventHandler>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/foreach.hpp>
namespace simgear
{
namespace canvas
@@ -43,9 +41,6 @@ namespace canvas
const Style& parent_style,
Element* parent ):
Image(canvas, node, parent_style, parent),
_attributes_dirty(0),
_resizable(false),
_capture_events(true),
_resize_top(node, "resize-top"),
_resize_right(node, "resize-right"),
_resize_bottom(node, "resize-bottom"),
@@ -92,7 +87,7 @@ namespace canvas
_capture_events = node->getBoolValue();
else if( name == "decoration-border" )
parseDecorationBorder(node->getStringValue());
else if( boost::starts_with(name, "shadow-")
else if( strutils::starts_with(name, "shadow-")
|| name == "content-size" )
_attributes_dirty |= DECORATION;
else
@@ -103,16 +98,10 @@ namespace canvas
Image::valueChanged(node);
}
//----------------------------------------------------------------------------
osg::Group* Window::getGroup()
{
return getMatrixTransform();
}
//----------------------------------------------------------------------------
const SGVec2<float> Window::getPosition() const
{
const osg::Matrix& m = getMatrixTransform()->getMatrix();
auto const& m = getMatrix();
return SGVec2<float>( m(3, 0), m(3, 1) );
}

View File

@@ -70,7 +70,6 @@ namespace canvas
virtual void update(double delta_time_sec);
virtual void valueChanged(SGPropertyNode* node);
osg::Group* getGroup();
const SGVec2<float> getPosition() const;
const SGRect<float> getScreenRegion() const;
@@ -104,7 +103,7 @@ namespace canvas
DECORATION = 1
};
uint32_t _attributes_dirty;
uint32_t _attributes_dirty {0};
CanvasPtr _canvas_decoration;
CanvasWeakPtr _canvas_content;
@@ -113,8 +112,8 @@ namespace canvas
ImagePtr _image_content,
_image_shadow;
bool _resizable,
_capture_events;
bool _resizable {false},
_capture_events {true};
PropertyObject<int> _resize_top,
_resize_right,

View File

@@ -9,10 +9,6 @@ set(HEADERS
CanvasText.hxx
)
set(DETAIL_HEADERS
detail/add_segment_variadic.hxx
)
set(SOURCES
CanvasElement.cxx
CanvasGroup.cxx
@@ -23,7 +19,6 @@ set(SOURCES
)
simgear_scene_component(canvas-elements canvas/elements "${SOURCES}" "${HEADERS}")
simgear_component(canvas-elements/detail canvas/elements/detail "" "${DETAIL_HEADERS}")
add_boost_test(canvas_element
SOURCES canvas_element_test.cpp

View File

@@ -31,10 +31,6 @@
#include <osg/StateAttribute>
#include <osg/Version>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <cassert>
#include <cmath>
#include <cstring>
@@ -214,22 +210,22 @@ namespace canvas
//----------------------------------------------------------------------------
void Element::onDestroy()
{
if( !_transform.valid() )
if( !_scene_group.valid() )
return;
// The transform node keeps a reference on this element, so ensure it is
// deleted.
BOOST_FOREACH(osg::Group* parent, _transform->getParents())
for(osg::Group* parent: _scene_group->getParents())
{
parent->removeChild(_transform.get());
parent->removeChild(_scene_group.get());
}
// Hide in case someone still holds a reference
setVisible(false);
removeListener();
_parent = 0;
_transform = 0;
_parent = nullptr;
_scene_group = nullptr;
}
//----------------------------------------------------------------------------
@@ -247,29 +243,8 @@ namespace canvas
//----------------------------------------------------------------------------
void Element::update(double dt)
{
if( !isVisible() )
return;
// Trigger matrix update
getMatrix();
// Update bounding box on manual update (manual updates pass zero dt)
if( dt == 0 && _drawable )
_drawable->getBound();
if( (_attributes_dirty & BLEND_FUNC) && _transform.valid() )
{
parseBlendFunc(
_transform->getOrCreateStateSet(),
_node->getChild("blend-source"),
_node->getChild("blend-destination"),
_node->getChild("blend-source-rgb"),
_node->getChild("blend-destination-rgb"),
_node->getChild("blend-source-alpha"),
_node->getChild("blend-destination-alpha")
);
_attributes_dirty &= ~BLEND_FUNC;
}
if( isVisible() )
updateImpl(dt);
}
//----------------------------------------------------------------------------
@@ -341,7 +316,8 @@ namespace canvas
if( listeners == _listener.end() )
return false;
BOOST_FOREACH(EventListener const& listener, listeners->second)
for(auto const& listener: listeners->second)
{
try
{
listener(event);
@@ -354,6 +330,7 @@ namespace canvas
"canvas::Element: event handler error: '" << ex.what() << "'"
);
}
}
return true;
}
@@ -395,9 +372,9 @@ namespace canvas
getBoundingBox()
#endif
.contains(osg::Vec3f(local_pos, 0));
else if( _transform.valid() )
else if( _scene_group.valid() )
// ... for other elements, i.e. groups only a bounding sphere is available
return _transform->getBound().contains(osg::Vec3f(parent_pos, 0));
return _scene_group->getBound().contains(osg::Vec3f(parent_pos, 0));
else
return false;
}
@@ -406,34 +383,32 @@ namespace canvas
//----------------------------------------------------------------------------
void Element::setVisible(bool visible)
{
if( _transform.valid() )
if( _scene_group.valid() )
// TODO check if we need another nodemask
_transform->setNodeMask(visible ? 0xffffffff : 0);
_scene_group->setNodeMask(visible ? 0xffffffff : 0);
}
//----------------------------------------------------------------------------
bool Element::isVisible() const
{
return _transform.valid() && _transform->getNodeMask() != 0;
return _scene_group.valid() && _scene_group->getNodeMask() != 0;
}
//----------------------------------------------------------------------------
osg::MatrixTransform* Element::getMatrixTransform()
osg::MatrixTransform* Element::getSceneGroup() const
{
return _transform.get();
}
//----------------------------------------------------------------------------
osg::MatrixTransform const* Element::getMatrixTransform() const
{
return _transform.get();
return _scene_group.get();
}
//----------------------------------------------------------------------------
osg::Vec2f Element::posToLocal(const osg::Vec2f& pos) const
{
getMatrix();
const osg::Matrix& m = _transform->getInverseMatrix();
if( !_scene_group )
// TODO log warning?
return pos;
updateMatrix();
const osg::Matrix& m = _scene_group->getInverseMatrix();
return osg::Vec2f
(
m(0, 0) * pos[0] + m(1, 0) * pos[1] + m(3, 0),
@@ -486,9 +461,6 @@ namespace canvas
{
if( child->getNameString() == NAME_TRANSFORM )
{
if( !_transform.valid() )
return;
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
{
SG_LOG
@@ -525,7 +497,7 @@ namespace canvas
if( parent == _node )
{
const std::string& name = child->getNameString();
if( boost::starts_with(name, "data-") )
if( strutils::starts_with(name, "data-") )
return;
else if( StyleInfo const* style_info = getStyleInfo(name) )
{
@@ -540,7 +512,7 @@ namespace canvas
}
else if( name == "update" )
return update(0);
else if( boost::starts_with(name, "blend-") )
else if( strutils::starts_with(name, "blend-") )
return (void)(_attributes_dirty |= BLEND_FUNC);
}
else if( parent
@@ -564,6 +536,10 @@ namespace canvas
//----------------------------------------------------------------------------
void Element::setClip(const std::string& clip)
{
if( !_scene_group )
// TODO warn?
return;
osg::StateSet* ss = getOrCreateStateSet();
if( !ss )
return;
@@ -577,8 +553,8 @@ namespace canvas
// TODO generalize CSS property parsing
const std::string RECT("rect(");
if( !boost::ends_with(clip, ")")
|| !boost::starts_with(clip, RECT) )
if( !strutils::ends_with(clip, ")")
|| !strutils::starts_with(clip, RECT) )
{
SG_LOG(SG_GENERAL, SG_WARN, "Canvas: invalid clip: " << clip);
return;
@@ -618,7 +594,7 @@ namespace canvas
}
if( !_scissor )
_scissor = new RelativeScissor(_transform.get());
_scissor = new RelativeScissor(_scene_group.get());
// <top>, <right>, <bottom>, <left>
_scissor->x() = values[3];
@@ -642,26 +618,26 @@ namespace canvas
_scissor->_coord_reference = rf;
}
//----------------------------------------------------------------------------
void Element::setRotation(unsigned int index, double r)
{
_node->getChild(NAME_TRANSFORM, index, true)->setDoubleValue("rot", r);
}
//----------------------------------------------------------------------------
void Element::setRotation(unsigned int index, double r)
{
_node->getChild(NAME_TRANSFORM, index, true)->setDoubleValue("rot", r);
}
//----------------------------------------------------------------------------
void Element::setTranslation(unsigned int index, double x, double y)
{
SGPropertyNode* tf = _node->getChild(NAME_TRANSFORM, index, true);
tf->getChild("t", 0, true)->setDoubleValue(x);
tf->getChild("t", 1, true)->setDoubleValue(y);
}
//----------------------------------------------------------------------------
void Element::setTranslation(unsigned int index, double x, double y)
{
SGPropertyNode* tf = _node->getChild(NAME_TRANSFORM, index, true);
tf->getChild("t", 0, true)->setDoubleValue(x);
tf->getChild("t", 1, true)->setDoubleValue(y);
}
//----------------------------------------------------------------------------
void Element::setTransformEnabled(unsigned int index, bool enabled)
{
SGPropertyNode* tf = _node->getChild(NAME_TRANSFORM, index, true);
tf->setBoolValue("enabled", enabled);
}
//----------------------------------------------------------------------------
void Element::setTransformEnabled(unsigned int index, bool enabled)
{
SGPropertyNode* tf = _node->getChild(NAME_TRANSFORM, index, true);
tf->setBoolValue("enabled", enabled);
}
//----------------------------------------------------------------------------
osg::BoundingBox Element::getBoundingBox() const
@@ -675,8 +651,8 @@ namespace canvas
osg::BoundingBox bb;
if( _transform.valid() )
bb.expandBy(_transform->getBound());
if( _scene_group.valid() )
bb.expandBy( _scene_group->getBound() );
return bb;
}
@@ -710,73 +686,11 @@ namespace canvas
//----------------------------------------------------------------------------
osg::Matrix Element::getMatrix() const
{
if( !_transform )
if( !_scene_group )
return osg::Matrix::identity();
if( !(_attributes_dirty & TRANSFORM) )
return _transform->getMatrix();
osg::Matrix m;
for( size_t i = 0; i < _transform_types.size(); ++i )
{
// Skip unused indizes...
if( _transform_types[i] == TT_NONE )
continue;
SGPropertyNode* tf_node = _node->getChild("tf", i, true);
if (!tf_node->getBoolValue("enabled", true)) {
continue; // skip disabled transforms
}
// Build up the matrix representation of the current transform node
osg::Matrix tf;
switch( _transform_types[i] )
{
case TT_MATRIX:
tf = osg::Matrix( tf_node->getDoubleValue("m[0]", 1),
tf_node->getDoubleValue("m[1]", 0),
0,
tf_node->getDoubleValue("m[6]", 0),
tf_node->getDoubleValue("m[2]", 0),
tf_node->getDoubleValue("m[3]", 1),
0,
tf_node->getDoubleValue("m[7]", 0),
0,
0,
1,
0,
tf_node->getDoubleValue("m[4]", 0),
tf_node->getDoubleValue("m[5]", 0),
0,
tf_node->getDoubleValue("m[8]", 1) );
break;
case TT_TRANSLATE:
tf.makeTranslate( osg::Vec3f( tf_node->getDoubleValue("t[0]", 0),
tf_node->getDoubleValue("t[1]", 0),
0 ) );
break;
case TT_ROTATE:
tf.makeRotate( tf_node->getDoubleValue("rot", 0), 0, 0, 1 );
break;
case TT_SCALE:
{
float sx = tf_node->getDoubleValue("s[0]", 1);
// sy defaults to sx...
tf.makeScale( sx, tf_node->getDoubleValue("s[1]", sx), 1 );
break;
}
default:
break;
}
m.postMult( tf );
}
_transform->setMatrix(m);
_attributes_dirty &= ~TRANSFORM;
return m;
updateMatrix();
return _scene_group->getMatrix();
}
//----------------------------------------------------------------------------
@@ -790,11 +704,8 @@ namespace canvas
PropertyBasedElement(node),
_canvas( canvas ),
_parent( parent ),
_attributes_dirty( 0 ),
_transform( new osg::MatrixTransform ),
_style( parent_style ),
_scissor( 0 ),
_drawable( 0 )
_scene_group( new osg::MatrixTransform ),
_style( parent_style )
{
staticInit();
@@ -806,15 +717,15 @@ namespace canvas
);
// Ensure elements are drawn in order they appear in the element tree
_transform->getOrCreateStateSet()
->setRenderBinDetails
(
0,
"PreOrderBin",
osg::StateSet::OVERRIDE_RENDERBIN_DETAILS
);
_scene_group
->getOrCreateStateSet()
->setRenderBinDetails(
0,
"PreOrderBin",
osg::StateSet::OVERRIDE_RENDERBIN_DETAILS
);
_transform->setUserData( new OSGUserData(this) );
_scene_group->setUserData( new OSGUserData(this) );
}
//----------------------------------------------------------------------------
@@ -902,11 +813,21 @@ namespace canvas
void Element::setDrawable( osg::Drawable* drawable )
{
_drawable = drawable;
assert( _drawable );
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
if( !_drawable )
{
SG_LOG(SG_GL, SG_WARN, "canvas::Element::setDrawable: NULL drawable");
return;
}
if( !_scene_group )
{
SG_LOG(SG_GL, SG_WARN, "canvas::Element::setDrawable: NULL scenegroup");
return;
}
auto geode = new osg::Geode;
geode->addDrawable(_drawable);
_transform->addChild(geode);
_scene_group->addChild(geode);
}
//----------------------------------------------------------------------------
@@ -914,18 +835,109 @@ namespace canvas
{
if( _drawable.valid() )
return _drawable->getOrCreateStateSet();
if( _transform.valid() )
return _transform->getOrCreateStateSet();
return 0;
else if( _scene_group.valid() )
return _scene_group->getOrCreateStateSet();
else
return nullptr;
}
//----------------------------------------------------------------------------
void Element::setupStyle()
{
BOOST_FOREACH( Style::value_type style, _style )
for(auto const& style: _style)
setStyle(style.second);
}
//----------------------------------------------------------------------------
void Element::updateMatrix() const
{
if( !(_attributes_dirty & TRANSFORM) || !_scene_group )
return;
osg::Matrix m;
for( size_t i = 0; i < _transform_types.size(); ++i )
{
// Skip unused indizes...
if( _transform_types[i] == TT_NONE )
continue;
SGPropertyNode* tf_node = _node->getChild("tf", i, true);
if (!tf_node->getBoolValue("enabled", true)) {
continue; // skip disabled transforms
}
// Build up the matrix representation of the current transform node
osg::Matrix tf;
switch( _transform_types[i] )
{
case TT_MATRIX:
tf = osg::Matrix( tf_node->getDoubleValue("m[0]", 1),
tf_node->getDoubleValue("m[1]", 0),
0,
tf_node->getDoubleValue("m[6]", 0),
tf_node->getDoubleValue("m[2]", 0),
tf_node->getDoubleValue("m[3]", 1),
0,
tf_node->getDoubleValue("m[7]", 0),
0,
0,
1,
0,
tf_node->getDoubleValue("m[4]", 0),
tf_node->getDoubleValue("m[5]", 0),
0,
tf_node->getDoubleValue("m[8]", 1) );
break;
case TT_TRANSLATE:
tf.makeTranslate( osg::Vec3f( tf_node->getDoubleValue("t[0]", 0),
tf_node->getDoubleValue("t[1]", 0),
0 ) );
break;
case TT_ROTATE:
tf.makeRotate( tf_node->getDoubleValue("rot", 0), 0, 0, 1 );
break;
case TT_SCALE:
{
float sx = tf_node->getDoubleValue("s[0]", 1);
// sy defaults to sx...
tf.makeScale( sx, tf_node->getDoubleValue("s[1]", sx), 1 );
break;
}
default:
break;
}
m.postMult( tf );
}
_scene_group->setMatrix(m);
_attributes_dirty &= ~TRANSFORM;
}
//----------------------------------------------------------------------------
void Element::updateImpl(double dt)
{
updateMatrix();
// Update bounding box on manual update (manual updates pass zero dt)
if( dt == 0 && _drawable )
_drawable->getBound();
if( (_attributes_dirty & BLEND_FUNC) )
{
parseBlendFunc(
_scene_group->getOrCreateStateSet(),
_node->getChild("blend-source"),
_node->getChild("blend-destination"),
_node->getChild("blend-source-rgb"),
_node->getChild("blend-destination-rgb"),
_node->getChild("blend-source-alpha"),
_node->getChild("blend-destination-alpha")
);
_attributes_dirty &= ~BLEND_FUNC;
}
}
} // namespace canvas
} // namespace simgear

View File

@@ -24,13 +24,13 @@
#include <simgear/canvas/CanvasEvent.hxx>
#include <simgear/props/PropertyBasedElement.hxx>
#include <simgear/misc/stdint.hxx> // for uint32_t
#include <simgear/std/type_traits.hxx>
#include <osg/BoundingBox>
#include <osg/MatrixTransform>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/type_traits/is_base_of.hpp>
namespace osg
{
@@ -49,6 +49,7 @@ namespace canvas
public PropertyBasedElement
{
public:
using SceneGroupWeakPtr = osg::observer_ptr<osg::MatrixTransform>;
/**
* Store pointer to window as user data
@@ -142,8 +143,11 @@ namespace canvas
*/
virtual bool isVisible() const;
osg::MatrixTransform* getMatrixTransform();
osg::MatrixTransform const* getMatrixTransform() const;
/**
* Get the according group in the OSG scene graph
*/
// TODO ref_ptr
osg::MatrixTransform* getSceneGroup() const;
/**
* Transform position to local coordinages.
@@ -217,13 +221,14 @@ namespace canvas
*/
template<typename Derived>
static
typename boost::enable_if<
boost::is_base_of<Element, Derived>,
std::enable_if_t<
std::is_base_of<Element, Derived>::value,
ElementPtr
>::type create( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& style = Style(),
Element* parent = NULL )
>
create( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& style = Style(),
Element* parent = NULL )
{
return ElementPtr( new Derived(canvas, node, style, parent) );
}
@@ -251,13 +256,13 @@ namespace canvas
CanvasWeakPtr _canvas;
ElementWeakPtr _parent;
mutable uint32_t _attributes_dirty;
mutable uint32_t _attributes_dirty = 0;
osg::observer_ptr<osg::MatrixTransform> _transform;
std::vector<TransformType> _transform_types;
SceneGroupWeakPtr _scene_group;
std::vector<TransformType> _transform_types;
Style _style;
RelativeScissor *_scissor;
RelativeScissor *_scissor = nullptr;
typedef std::vector<EventListener> Listener;
typedef std::map<int, Listener> ListenerMap;
@@ -584,6 +589,10 @@ namespace canvas
void setupStyle();
void updateMatrix() const;
virtual void updateImpl(double dt);
private:
osg::ref_ptr<osg::Drawable> _drawable;

View File

@@ -23,13 +23,10 @@
#include "CanvasMap.hxx"
#include "CanvasPath.hxx"
#include "CanvasText.hxx"
#include <simgear/canvas/CanvasEventVisitor.hxx>
#include <simgear/canvas/events/MouseEvent.hxx>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <boost/lambda/core.hpp>
namespace simgear
{
namespace canvas
@@ -48,7 +45,7 @@ namespace canvas
ElementFactories Group::_child_factories;
const std::string Group::TYPE_NAME = "group";
void warnTransformExpired(const char* member_name)
void warnSceneGroupExpired(const char* member_name)
{
SG_LOG( SG_GENERAL,
SG_WARN,
@@ -135,63 +132,57 @@ namespace canvas
//----------------------------------------------------------------------------
ElementPtr Group::getElementById(const std::string& id)
{
if( !_transform.valid() )
if( !_scene_group.valid() )
{
warnTransformExpired("getElementById");
return ElementPtr();
warnSceneGroupExpired("getElementById");
return {};
}
std::vector<GroupPtr> groups;
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
// TODO check search algorithm. Not completely breadth-first and might be
// possible with using less dynamic memory
std::vector<GroupPtr> child_groups;
for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
{
const ElementPtr& el = getChildByIndex(i);
if( el->get<std::string>("id") == id )
return el;
Group* group = dynamic_cast<Group*>(el.get());
if( group )
groups.push_back(group);
if( Group* child_group = dynamic_cast<Group*>(el.get()) )
child_groups.push_back(child_group);
}
BOOST_FOREACH( GroupPtr group, groups )
for(auto group: child_groups)
{
ElementPtr el = group->getElementById(id);
if( el )
if( ElementPtr el = group->getElementById(id) )
return el;
}
return ElementPtr();
return {};
}
//----------------------------------------------------------------------------
void Group::clearEventListener()
{
if( !_transform.valid() )
return warnTransformExpired("clearEventListener");
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
getChildByIndex(i)->clearEventListener();
Element::clearEventListener();
}
//----------------------------------------------------------------------------
void Group::update(double dt)
{
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
getChildByIndex(i)->update(dt);
if( !_scene_group.valid() )
return warnSceneGroupExpired("clearEventListener");
Element::update(dt);
for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
getChildByIndex(i)->clearEventListener();
}
//----------------------------------------------------------------------------
bool Group::traverse(EventVisitor& visitor)
{
// Iterate in reverse order as last child is displayed on top
for(size_t i = _transform->getNumChildren(); i --> 0;)
if( _scene_group.valid() )
{
if( getChildByIndex(i)->accept(visitor) )
return true;
// Iterate in reverse order as last child is displayed on top
for(size_t i = _scene_group->getNumChildren(); i --> 0;)
{
if( getChildByIndex(i)->accept(visitor) )
return true;
}
}
return false;
}
@@ -206,13 +197,13 @@ namespace canvas
bool handled = setStyleImpl(style, style_info);
if( style_info->inheritable )
{
if( !_transform.valid() )
if( !_scene_group.valid() )
{
warnTransformExpired("setStyle");
warnSceneGroupExpired("setStyle");
return false;
}
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
handled |= getChildByIndex(i)->setStyle(style, style_info);
}
@@ -222,26 +213,20 @@ namespace canvas
//----------------------------------------------------------------------------
osg::BoundingBox Group::getTransformedBounds(const osg::Matrix& m) const
{
osg::BoundingBox bb;
if( !_transform.valid() )
if( !_scene_group.valid() )
{
warnTransformExpired("getTransformedBounds");
return bb;
warnSceneGroupExpired("getTransformedBounds");
return {};
}
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
osg::BoundingBox bb;
for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
{
const ElementPtr& child = getChildByIndex(i);
if( !child->getMatrixTransform()->getNodeMask() )
auto child = getChildByIndex(i);
if( !child || !child->isVisible() )
continue;
bb.expandBy
(
child->getTransformedBounds
(
child->getMatrixTransform()->getMatrix() * m
)
);
bb.expandBy( child->getTransformedBounds(child->getMatrix() * m) );
}
return bb;
@@ -257,6 +242,15 @@ namespace canvas
return ElementFactory();
}
//----------------------------------------------------------------------------
void Group::updateImpl(double dt)
{
Element::updateImpl(dt);
for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
getChildByIndex(i)->update(dt);
}
//----------------------------------------------------------------------------
void Group::childAdded(SGPropertyNode* child)
{
@@ -266,13 +260,13 @@ namespace canvas
ElementFactory child_factory = getChildFactory( child->getNameString() );
if( child_factory )
{
if( !_transform.valid() )
return warnTransformExpired("childAdded");
if( !_scene_group.valid() )
return warnSceneGroupExpired("childAdded");
ElementPtr element = child_factory(_canvas, child, _style, this);
// Add to osg scene graph...
_transform->addChild( element->getMatrixTransform() );
_scene_group->addChild(element->getSceneGroup());
// ...and ensure correct ordering
handleZIndexChanged(element);
@@ -293,7 +287,7 @@ namespace canvas
if( getChildFactory(node->getNameString()) )
{
if( !_transform.valid() )
if( !_scene_group.valid() )
// If transform is destroyed also all children are destroyed, so we can
// not do anything here.
return;
@@ -323,7 +317,7 @@ namespace canvas
void Group::childChanged(SGPropertyNode* node)
{
SGPropertyNode* parent = node->getParent();
SGPropertyNode* grand_parent = parent ? parent->getParent() : NULL;
SGPropertyNode* grand_parent = parent ? parent->getParent() : nullptr;
if( grand_parent == _node
&& node->getNameString() == "z-index" )
@@ -333,16 +327,18 @@ namespace canvas
//----------------------------------------------------------------------------
void Group::handleZIndexChanged(ElementPtr child, int z_index)
{
if( !child || !_transform.valid() )
if( !child || !_scene_group.valid() )
return;
osg::ref_ptr<osg::MatrixTransform> tf = child->getMatrixTransform();
size_t index = _transform->getChildIndex(tf),
// Keep reference to prevent deleting while removing and re-inserting later
osg::ref_ptr<osg::MatrixTransform> tf = child->getSceneGroup();
size_t index = _scene_group->getChildIndex(tf),
index_new = index;
for(;; ++index_new)
{
if( index_new + 1 == _transform->getNumChildren() )
if( index_new + 1 == _scene_group->getNumChildren() )
break;
// Move to end of block with same index (= move upwards until the next
@@ -369,8 +365,8 @@ namespace canvas
return;
}
_transform->removeChild(index);
_transform->insertChild(index_new, tf);
_scene_group->removeChild(index);
_scene_group->insertChild(index_new, tf);
SG_LOG
(
@@ -383,24 +379,27 @@ namespace canvas
//----------------------------------------------------------------------------
ElementPtr Group::getChildByIndex(size_t index) const
{
assert(_transform.valid());
OSGUserData* ud =
static_cast<OSGUserData*>(_transform->getChild(index)->getUserData());
assert(ud);
return ud->element;
assert( _scene_group.valid() );
auto child = _scene_group->getChild(index);
if( !child )
return {};
auto ud = static_cast<OSGUserData*>(child->getUserData());
return ud ? ud->element : ElementPtr();
}
//----------------------------------------------------------------------------
ElementPtr Group::findChild( const SGPropertyNode* node,
const std::string& id ) const
{
if( !_transform.valid() )
if( !_scene_group.valid() )
{
warnTransformExpired("findChild");
return ElementPtr();
warnSceneGroupExpired("findChild");
return {};
}
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
for(size_t i = 0; i < _scene_group->getNumChildren(); ++i)
{
ElementPtr el = getChildByIndex(i);
@@ -416,7 +415,7 @@ namespace canvas
}
}
return ElementPtr();
return {};
}
} // namespace canvas

View File

@@ -88,8 +88,6 @@ namespace canvas
virtual void clearEventListener();
virtual void update(double dt);
virtual bool traverse(EventVisitor& visitor);
virtual bool setStyle( const SGPropertyNode* child,
@@ -107,6 +105,8 @@ namespace canvas
*/
virtual ElementFactory getChildFactory(const std::string& type) const;
virtual void updateImpl(double dt);
virtual void childAdded(SGPropertyNode * child);
virtual void childRemoved(SGPropertyNode * child);
virtual void childChanged(SGPropertyNode * child);

View File

@@ -117,9 +117,7 @@ namespace canvas
ElementWeakPtr parent ):
Element(canvas, node, parent_style, parent),
_texture(new osg::Texture2D),
_node_src_rect( node->getNode("source", 0, true) ),
_src_rect(0,0),
_region(0,0)
_node_src_rect( node->getNode("source", 0, true) )
{
staticInit();
@@ -157,22 +155,207 @@ namespace canvas
//----------------------------------------------------------------------------
Image::~Image()
{
if( _http_request ) {
Canvas::getSystemAdapter()->getHTTPClient()->cancelRequest(_http_request, "image destroyed");
}
if( _http_request )
{
Canvas::getSystemAdapter()
->getHTTPClient()
->cancelRequest(_http_request, "image destroyed");
}
}
//----------------------------------------------------------------------------
void Image::update(double dt)
void Image::valueChanged(SGPropertyNode* child)
{
Element::update(dt);
// If the image is switched from invisible to visible, and it shows a
// canvas, we need to delay showing it by one frame to ensure the canvas is
// updated before the image is displayed.
//
// As canvas::Element handles and filters changes to the "visible" property
// we can not check this in Image::childChanged but instead have to override
// Element::valueChanged.
if( !isVisible()
&& child->getParent() == _node
&& child->getNameString() == "visible"
&& child->getBoolValue() )
{
CullCallback* cb =
#if OSG_VERSION_LESS_THAN(3,3,2)
static_cast<CullCallback*>
#else
dynamic_cast<CullCallback*>
#endif
( _geom->getCullCallback() );
if( cb )
cb->cullNextFrame();
}
Element::valueChanged(child);
}
//----------------------------------------------------------------------------
void Image::setSrcCanvas(CanvasPtr canvas)
{
CanvasPtr src_canvas = _src_canvas.lock(),
self_canvas = _canvas.lock();
if( src_canvas )
src_canvas->removeParentCanvas(self_canvas);
if( self_canvas )
self_canvas->removeChildCanvas(src_canvas);
_src_canvas = src_canvas = canvas;
_attributes_dirty |= SRC_CANVAS;
_geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
if( src_canvas )
{
setupDefaultDimensions();
if( self_canvas )
{
self_canvas->addChildCanvas(src_canvas);
src_canvas->addParentCanvas(self_canvas);
}
}
}
//----------------------------------------------------------------------------
CanvasWeakPtr Image::getSrcCanvas() const
{
return _src_canvas;
}
//----------------------------------------------------------------------------
void Image::setImage(osg::ref_ptr<osg::Image> img)
{
// remove canvas...
setSrcCanvas( CanvasPtr() );
_texture->setResizeNonPowerOfTwoHint(false);
_texture->setImage(img);
_texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
_texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
_geom->getOrCreateStateSet()
->setTextureAttributeAndModes(0, _texture);
if( img )
setupDefaultDimensions();
}
//----------------------------------------------------------------------------
void Image::setFill(const std::string& fill)
{
osg::Vec4 color(1,1,1,1);
if( !fill.empty() // If no color is given default to white
&& !parseColor(fill, color) )
return;
setFill(color);
}
//----------------------------------------------------------------------------
void Image::setFill(const osg::Vec4& color)
{
_colors->front() = color;
_colors->dirty();
}
//----------------------------------------------------------------------------
void Image::setOutset(const std::string& outset)
{
_outset = CSSBorder::parse(outset);
_attributes_dirty |= DEST_SIZE;
}
//----------------------------------------------------------------------------
void Image::setPreserveAspectRatio(const std::string& scale)
{
_preserve_aspect_ratio = SVGpreserveAspectRatio::parse(scale);
_attributes_dirty |= SRC_RECT;
}
//----------------------------------------------------------------------------
void Image::setSourceRect(const SGRect<float>& sourceRect)
{
_attributes_dirty |= SRC_RECT;
_src_rect = sourceRect;
}
//----------------------------------------------------------------------------
void Image::setSlice(const std::string& slice)
{
_slice = CSSBorder::parse(slice);
_attributes_dirty |= SRC_RECT | DEST_SIZE;
}
//----------------------------------------------------------------------------
void Image::setSliceWidth(const std::string& width)
{
_slice_width = CSSBorder::parse(width);
_attributes_dirty |= DEST_SIZE;
}
//----------------------------------------------------------------------------
const SGRect<float>& Image::getRegion() const
{
return _region;
}
//----------------------------------------------------------------------------
bool Image::handleEvent(const EventPtr& event)
{
bool handled = Element::handleEvent(event);
CanvasPtr src_canvas = _src_canvas.lock();
if( !src_canvas )
return handled;
if( MouseEventPtr mouse_event = dynamic_cast<MouseEvent*>(event.get()) )
{
mouse_event.reset( new MouseEvent(*mouse_event) );
mouse_event->client_pos = mouse_event->local_pos
- toOsg(_region.getMin());
osg::Vec2f size(_region.width(), _region.height());
if( _outset.isValid() )
{
CSSBorder::Offsets outset =
_outset.getAbsOffsets(getTextureDimensions());
mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
size.x() += outset.l + outset.r;
size.y() += outset.t + outset.b;
}
// Scale event pos according to canvas view size vs. displayed/screen size
mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
mouse_event->local_pos = mouse_event->client_pos;
handled |= src_canvas->handleMouseEvent(mouse_event);
}
else if( KeyboardEventPtr keyboard_event =
dynamic_cast<KeyboardEvent*>(event.get()) )
{
handled |= src_canvas->handleKeyboardEvent(keyboard_event);
}
return handled;
}
//----------------------------------------------------------------------------
void Image::updateImpl(double dt)
{
Element::updateImpl(dt);
osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
(
_geom->getOrCreateStateSet()
->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
);
simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
auto canvas = _src_canvas.lock();
if( (_attributes_dirty & SRC_CANVAS)
// check if texture has changed (eg. due to resizing)
@@ -402,188 +585,6 @@ namespace canvas
}
}
//----------------------------------------------------------------------------
void Image::valueChanged(SGPropertyNode* child)
{
// If the image is switched from invisible to visible, and it shows a
// canvas, we need to delay showing it by one frame to ensure the canvas is
// updated before the image is displayed.
//
// As canvas::Element handles and filters changes to the "visible" property
// we can not check this in Image::childChanged but instead have to override
// Element::valueChanged.
if( !isVisible()
&& child->getParent() == _node
&& child->getNameString() == "visible"
&& child->getBoolValue() )
{
CullCallback* cb =
#if OSG_VERSION_LESS_THAN(3,3,2)
static_cast<CullCallback*>
#else
dynamic_cast<CullCallback*>
#endif
( _geom->getCullCallback() );
if( cb )
cb->cullNextFrame();
}
Element::valueChanged(child);
}
//----------------------------------------------------------------------------
void Image::setSrcCanvas(CanvasPtr canvas)
{
CanvasPtr src_canvas = _src_canvas.lock(),
self_canvas = _canvas.lock();
if( src_canvas )
src_canvas->removeParentCanvas(self_canvas);
if( self_canvas )
self_canvas->removeChildCanvas(src_canvas);
_src_canvas = src_canvas = canvas;
_attributes_dirty |= SRC_CANVAS;
_geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
if( src_canvas )
{
setupDefaultDimensions();
if( self_canvas )
{
self_canvas->addChildCanvas(src_canvas);
src_canvas->addParentCanvas(self_canvas);
}
}
}
//----------------------------------------------------------------------------
CanvasWeakPtr Image::getSrcCanvas() const
{
return _src_canvas;
}
//----------------------------------------------------------------------------
void Image::setImage(osg::ref_ptr<osg::Image> img)
{
// remove canvas...
setSrcCanvas( CanvasPtr() );
_texture->setResizeNonPowerOfTwoHint(false);
_texture->setImage(img);
_texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
_texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
_geom->getOrCreateStateSet()
->setTextureAttributeAndModes(0, _texture);
if( img )
setupDefaultDimensions();
}
//----------------------------------------------------------------------------
void Image::setFill(const std::string& fill)
{
osg::Vec4 color(1,1,1,1);
if( !fill.empty() // If no color is given default to white
&& !parseColor(fill, color) )
return;
setFill(color);
}
//----------------------------------------------------------------------------
void Image::setFill(const osg::Vec4& color)
{
_colors->front() = color;
_colors->dirty();
}
//----------------------------------------------------------------------------
void Image::setOutset(const std::string& outset)
{
_outset = CSSBorder::parse(outset);
_attributes_dirty |= DEST_SIZE;
}
//----------------------------------------------------------------------------
void Image::setPreserveAspectRatio(const std::string& scale)
{
_preserve_aspect_ratio = SVGpreserveAspectRatio::parse(scale);
_attributes_dirty |= SRC_RECT;
}
//----------------------------------------------------------------------------
void Image::setSourceRect(const SGRect<float>& sourceRect)
{
_attributes_dirty |= SRC_RECT;
_src_rect = sourceRect;
}
//----------------------------------------------------------------------------
void Image::setSlice(const std::string& slice)
{
_slice = CSSBorder::parse(slice);
_attributes_dirty |= SRC_RECT | DEST_SIZE;
}
//----------------------------------------------------------------------------
void Image::setSliceWidth(const std::string& width)
{
_slice_width = CSSBorder::parse(width);
_attributes_dirty |= DEST_SIZE;
}
//----------------------------------------------------------------------------
const SGRect<float>& Image::getRegion() const
{
return _region;
}
//----------------------------------------------------------------------------
bool Image::handleEvent(const EventPtr& event)
{
bool handled = Element::handleEvent(event);
CanvasPtr src_canvas = _src_canvas.lock();
if( !src_canvas )
return handled;
if( MouseEventPtr mouse_event = dynamic_cast<MouseEvent*>(event.get()) )
{
mouse_event.reset( new MouseEvent(*mouse_event) );
mouse_event->client_pos = mouse_event->local_pos
- toOsg(_region.getMin());
osg::Vec2f size(_region.width(), _region.height());
if( _outset.isValid() )
{
CSSBorder::Offsets outset =
_outset.getAbsOffsets(getTextureDimensions());
mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
size.x() += outset.l + outset.r;
size.y() += outset.t + outset.b;
}
// Scale event pos according to canvas view size vs. displayed/screen size
mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
mouse_event->local_pos = mouse_event->client_pos;
handled |= src_canvas->handleMouseEvent(mouse_event);
}
else if( KeyboardEventPtr keyboard_event =
dynamic_cast<KeyboardEvent*>(event.get()) )
{
handled |= src_canvas->handleKeyboardEvent(keyboard_event);
}
return handled;
}
//----------------------------------------------------------------------------
void Image::childChanged(SGPropertyNode* child)
{
@@ -634,7 +635,9 @@ namespace canvas
// Abort pending request
if( _http_request )
{
Canvas::getSystemAdapter()->getHTTPClient()->cancelRequest(_http_request, "setting new image");
Canvas::getSystemAdapter()
->getHTTPClient()
->cancelRequest(_http_request, "setting new image");
_http_request.reset();
}

View File

@@ -53,7 +53,6 @@ namespace canvas
ElementWeakPtr parent = 0 );
virtual ~Image();
virtual void update(double dt);
virtual void valueChanged(SGPropertyNode* child);
void setSrcCanvas(CanvasPtr canvas);
@@ -100,8 +99,8 @@ namespace canvas
*
*/
void setSourceRect(const SGRect<float>& sourceRect);
protected:
protected:
enum ImageAttributes
{
SRC_RECT = LAST_ATTRIBUTE << 1, // Source image rectangle
@@ -109,6 +108,8 @@ namespace canvas
SRC_CANVAS = DEST_SIZE << 1
};
virtual void updateImpl(double dt);
virtual void childChanged(SGPropertyNode * child);
void setupDefaultDimensions();
@@ -134,9 +135,9 @@ namespace canvas
osg::ref_ptr<osg::Vec2Array> _texCoords;
osg::ref_ptr<osg::Vec4Array> _colors;
SGPropertyNode *_node_src_rect;
SGRect<float> _src_rect,
_region;
SGPropertyNode *_node_src_rect = nullptr;
SGRect<float> _src_rect {0, 0},
_region {0, 0};
SVGpreserveAspectRatio _preserve_aspect_ratio;

View File

@@ -18,13 +18,14 @@
// 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"
#include <cmath>
#include <simgear/misc/strutils.hxx>
#include <boost/algorithm/string/predicate.hpp>
#include <cmath>
#define LOG_GEO_RET(msg) \
{\
@@ -47,6 +48,12 @@ namespace canvas
const std::string GEO = "-geo";
const std::string HDG = "hdg";
const std::string Map::TYPE_NAME = "map";
const std::string WEB_MERCATOR = "webmercator";
const std::string REF_LAT = "ref-lat";
const std::string REF_LON = "ref-lon";
const std::string SCREEN_RANGE = "screen-range";
const std::string RANGE = "range";
const std::string PROJECTION = "projection";
//----------------------------------------------------------------------------
void Map::staticInit()
@@ -64,12 +71,11 @@ namespace canvas
const SGPropertyNode_ptr& node,
const Style& parent_style,
ElementWeakPtr parent ):
Group(canvas, node, parent_style, parent),
// TODO make projection configurable
_projection(new SansonFlamsteedProjection),
_projection_dirty(true)
Group(canvas, node, parent_style, parent)
{
staticInit();
projectionNodeChanged(node->getChild(PROJECTION));
}
//----------------------------------------------------------------------------
@@ -79,13 +85,13 @@ namespace canvas
}
//----------------------------------------------------------------------------
void Map::update(double dt)
void Map::updateImpl(double dt)
{
for( GeoNodes::iterator it = _geo_nodes.begin();
it != _geo_nodes.end();
++it )
Group::updateImpl(dt);
for(auto& it: _geo_nodes)
{
GeoNodePair* geo_node = it->second.get();
GeoNodePair* geo_node = it.second.get();
if( !geo_node->isComplete()
|| (!geo_node->isDirty() && !_projection_dirty) )
continue;
@@ -107,14 +113,12 @@ namespace canvas
geo_node->setDirty(false);
}
_projection_dirty = false;
Group::update(dt);
}
//----------------------------------------------------------------------------
void Map::childAdded(SGPropertyNode* parent, SGPropertyNode* child)
{
if( boost::ends_with(child->getNameString(), GEO) )
if( strutils::ends_with(child->getNameString(), GEO) )
_geo_nodes[child].reset(new GeoNodePair());
else if( parent != _node && child->getNameString() == HDG )
_hdg_nodes.insert(child);
@@ -125,7 +129,7 @@ namespace canvas
//----------------------------------------------------------------------------
void Map::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
{
if( boost::ends_with(child->getNameString(), GEO) )
if( strutils::ends_with(child->getNameString(), GEO) )
// TODO remove from other node
_geo_nodes.erase(child);
else if( parent != _node && child->getName() == HDG )
@@ -147,7 +151,7 @@ namespace canvas
{
const std::string& name = child->getNameString();
if( boost::ends_with(name, GEO) )
if( strutils::ends_with(name, GEO) )
return geoNodeChanged(child);
else if( name == HDG )
return hdgNodeChanged(child);
@@ -162,10 +166,10 @@ namespace canvas
if( child->getParent() != _node )
return Group::childChanged(child);
if( child->getNameString() == "ref-lat"
|| child->getNameString() == "ref-lon" )
_projection->setWorldPosition( _node->getDoubleValue("ref-lat"),
_node->getDoubleValue("ref-lon") );
if( child->getNameString() == REF_LAT
|| child->getNameString() == REF_LON )
_projection->setWorldPosition( _node->getDoubleValue(REF_LAT),
_node->getDoubleValue(REF_LON) );
else if( child->getNameString() == HDG )
{
_projection->setOrientation(child->getFloatValue());
@@ -174,16 +178,35 @@ namespace canvas
++it )
hdgNodeChanged(*it);
}
else if( child->getNameString() == "range" )
else if( child->getNameString() == RANGE )
_projection->setRange(child->getDoubleValue());
else if( child->getNameString() == "screen-range" )
else if( child->getNameString() == SCREEN_RANGE )
_projection->setScreenRange(child->getDoubleValue());
else if( child->getNameString() == PROJECTION )
projectionNodeChanged(child);
else
return Group::childChanged(child);
_projection_dirty = true;
}
//----------------------------------------------------------------------------
void Map::projectionNodeChanged(SGPropertyNode* child)
{
if(child && child->getStringValue() == WEB_MERCATOR)
_projection = std::make_shared<WebMercatorProjection>();
else
_projection = std::make_shared<SansonFlamsteedProjection>();
_projection->setWorldPosition(_node->getDoubleValue(REF_LAT),
_node->getDoubleValue(REF_LON) );
_projection->setOrientation(_node->getFloatValue(HDG));
_projection->setScreenRange(_node->getDoubleValue(SCREEN_RANGE));
_projection->setRange(_node->getDoubleValue(RANGE));
_projection_dirty = true;
}
//----------------------------------------------------------------------------
void Map::geoNodeChanged(SGPropertyNode* child)
{

View File

@@ -22,9 +22,9 @@
#include "CanvasGroup.hxx"
#include <boost/shared_ptr.hpp>
#include <boost/unordered_map.hpp>
#include <boost/unordered_set.hpp>
#include <memory>
#include <unordered_map>
#include <unordered_set>
namespace simgear
{
@@ -45,45 +45,41 @@ namespace canvas
ElementWeakPtr parent = 0 );
virtual ~Map();
virtual void update(double dt);
virtual void childAdded( SGPropertyNode * parent,
SGPropertyNode * child );
virtual void childRemoved( SGPropertyNode * parent,
SGPropertyNode * child );
virtual void valueChanged(SGPropertyNode * child);
protected:
virtual void updateImpl(double dt);
virtual void childChanged(SGPropertyNode * child);
void updateProjection(SGPropertyNode* type_node);
typedef boost::unordered_map< SGPropertyNode*,
boost::shared_ptr<GeoNodePair>
> GeoNodes;
typedef boost::unordered_set<SGPropertyNode*> NodeSet;
virtual void childAdded( SGPropertyNode* parent,
SGPropertyNode* child );
virtual void childRemoved( SGPropertyNode* parent,
SGPropertyNode* child );
virtual void valueChanged(SGPropertyNode* child);
virtual void childChanged(SGPropertyNode* child);
using GeoNodes =
std::unordered_map<SGPropertyNode*, std::shared_ptr<GeoNodePair>>;
using NodeSet = std::unordered_set<SGPropertyNode*>;
GeoNodes _geo_nodes;
NodeSet _hdg_nodes;
boost::shared_ptr<HorizontalProjection> _projection;
bool _projection_dirty;
std::shared_ptr<HorizontalProjection> _projection;
bool _projection_dirty = false;
struct GeoCoord
{
GeoCoord():
type(INVALID),
value(0)
{}
enum
{
INVALID,
LATITUDE,
LONGITUDE
} type;
double value;
} type = INVALID;
double value = 0;
};
void geoNodeChanged(SGPropertyNode * child);
void hdgNodeChanged(SGPropertyNode * child);
void projectionNodeChanged(SGPropertyNode* child);
void geoNodeChanged(SGPropertyNode* child);
void hdgNodeChanged(SGPropertyNode* child);
GeoCoord parseGeoCoord(const std::string& val) const;
};

View File

@@ -206,23 +206,13 @@ namespace canvas
return SGVec2f(-1.0f, -1.0f);
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
class Path::PathDrawable:
public osg::Drawable
{
public:
PathDrawable(Path* path):
_path_element(path),
_path(VG_INVALID_HANDLE),
_paint(VG_INVALID_HANDLE),
_paint_fill(VG_INVALID_HANDLE),
_attributes_dirty(~0),
_mode(0),
_fill_rule(VG_EVEN_ODD),
_stroke_width(1),
_stroke_linecap(VG_CAP_BUTT),
_stroke_linejoin(VG_JOIN_MITER)
_path_element(path)
{
setSupportsDisplayList(false);
setDataVariance(Object::DYNAMIC);
@@ -283,6 +273,16 @@ namespace canvas
}
}
/**
* Set path fill opacity (Only used if fill is not "none")
*/
void setFillOpacity(float opacity)
{
_fill_opacity =
static_cast<uint8_t>(SGMiscf::clip(opacity, 0.f, 1.f) * 255);
_attributes_dirty |= FILL_COLOR;
}
/**
* Set path fill rule ("pseudo-nonzero" or "evenodd")
*
@@ -310,7 +310,7 @@ namespace canvas
else if( parseColor(stroke, _stroke_color) )
{
_mode |= VG_STROKE_PATH;
_attributes_dirty |= STROKE_COLOR;
_attributes_dirty |= STROKE_COLOR;
}
else
{
@@ -323,6 +323,16 @@ namespace canvas
}
}
/**
* Set path stroke opacity (only used if stroke is not "none")
*/
void setStrokeOpacity(float opacity)
{
_stroke_opacity =
static_cast<uint8_t>(SGMiscf::clip(opacity, 0.f, 1.f) * 255);
_attributes_dirty |= STROKE_COLOR;
}
/**
* Set stroke width
*/
@@ -390,31 +400,22 @@ namespace canvas
osg::StateAttribute const* blend_func =
state->getLastAppliedAttribute(osg::StateAttribute::BLENDFUNC);
// Initialize/Update the paint
if( _attributes_dirty & STROKE_COLOR )
{
if( _paint == VG_INVALID_HANDLE )
_paint = vgCreatePaint();
vgSetParameterfv(_paint, VG_PAINT_COLOR, 4, _stroke_color._v);
_attributes_dirty &= ~STROKE_COLOR;
}
// Initialize/update fill paint
if( _attributes_dirty & FILL_COLOR )
{
if( _paint_fill == VG_INVALID_HANDLE )
_paint_fill = vgCreatePaint();
vgSetParameterfv(_paint_fill, VG_PAINT_COLOR, 4, _fill_color._v);
_attributes_dirty &= ~FILL_COLOR;
}
// Setup paint
if( _mode & VG_STROKE_PATH )
{
// Initialize/Update the paint
if( _attributes_dirty & STROKE_COLOR )
{
if( _paint == VG_INVALID_HANDLE )
_paint = vgCreatePaint();
auto color = _stroke_color;
color.a() *= _stroke_opacity / 255.f;
vgSetParameterfv(_paint, VG_PAINT_COLOR, 4, color._v);
_attributes_dirty &= ~STROKE_COLOR;
}
vgSetPaint(_paint, VG_STROKE_PATH);
vgSetf(VG_STROKE_LINE_WIDTH, _stroke_width);
@@ -426,6 +427,19 @@ namespace canvas
}
if( _mode & VG_FILL_PATH )
{
// Initialize/update fill paint
if( _attributes_dirty & FILL_COLOR )
{
if( _paint_fill == VG_INVALID_HANDLE )
_paint_fill = vgCreatePaint();
auto color = _fill_color;
color.a() *= _fill_opacity / 255.f;
vgSetParameterfv(_paint_fill, VG_PAINT_COLOR, 4, color._v);
_attributes_dirty &= ~FILL_COLOR;
}
vgSetPaint(_paint_fill, VG_FILL_PATH);
vgSeti(VG_FILL_RULE, _fill_rule);
@@ -579,22 +593,24 @@ namespace canvas
Path *_path_element;
mutable VGPath _path;
mutable VGPaint _paint;
mutable VGPaint _paint_fill;
mutable uint32_t _attributes_dirty;
mutable VGPath _path {VG_INVALID_HANDLE};
mutable VGPaint _paint {VG_INVALID_HANDLE};
mutable VGPaint _paint_fill {VG_INVALID_HANDLE};
mutable uint32_t _attributes_dirty {~0u};
CmdList _cmds;
CoordList _coords;
VGbitfield _mode;
VGbitfield _mode {0};
osg::Vec4f _fill_color;
VGFillRule _fill_rule;
uint8_t _fill_opacity {255};
VGFillRule _fill_rule {VG_EVEN_ODD};
osg::Vec4f _stroke_color;
VGfloat _stroke_width;
uint8_t _stroke_opacity {255};
VGfloat _stroke_width {1};
std::vector<VGfloat> _stroke_dash;
VGCapStyle _stroke_linecap;
VGJoinStyle _stroke_linejoin;
VGCapStyle _stroke_linecap {VG_CAP_BUTT};
VGJoinStyle _stroke_linejoin {VG_JOIN_MITER};
osg::Vec3f transformPoint( const osg::Matrix& m,
osg::Vec2f pos ) const
@@ -670,8 +686,10 @@ namespace canvas
PathDrawableRef Path::*path = &Path::_path;
addStyle("fill", "color", &PathDrawable::setFill, path);
addStyle("fill-opacity", "numeric", &PathDrawable::setFillOpacity, path);
addStyle("fill-rule", "", &PathDrawable::setFillRule, path);
addStyle("stroke", "color", &PathDrawable::setStroke, path);
addStyle("stroke-opacity", "numeric", &PathDrawable::setStrokeOpacity, path);
addStyle("stroke-width", "numeric", &PathDrawable::setStrokeWidth, path);
addStyle("stroke-dasharray", "", &PathDrawable::setStrokeDashArray, path);
addStyle("stroke-linecap", "", &PathDrawable::setStrokeLinecap, path);
@@ -700,92 +718,69 @@ namespace canvas
}
//----------------------------------------------------------------------------
void Path::update(double dt)
{
if( _attributes_dirty & (CMDS | COORDS) )
{
_path->setSegments
(
_node->getChildValues<VGubyte, int>("cmd"),
_node->getChildValues<VGfloat, float>("coord")
);
_attributes_dirty &= ~(CMDS | COORDS);
}
// SVG path overrides manual cmd/coord specification
if ( _hasSVG && (_attributes_dirty & SVG))
{
CmdList cmds;
CoordList coords;
parseSVGPathToVGPath(_node->getStringValue("svg"), cmds, coords);
_path->setSegments(cmds, coords);
_attributes_dirty &= ~SVG;
}
if ( _hasRect &&(_attributes_dirty & RECT))
{
parseRectToVGPath();
_attributes_dirty &= ~RECT;
}
Element::update(dt);
}
//----------------------------------------------------------------------------
osg::BoundingBox Path::getTransformedBounds(const osg::Matrix& m) const
{
return _path->getTransformedBounds(m);
}
//----------------------------------------------------------------------------
Path& Path::addSegment(uint8_t cmd, std::initializer_list<float> coords)
{
_node->addChild("cmd")->setIntValue(cmd);
for(float coord: coords)
_node->addChild("coord")->setFloatValue(coord);
return *this;
}
//----------------------------------------------------------------------------
Path& Path::moveTo(float x_abs, float y_abs)
{
return addSegment(VG_MOVE_TO_ABS, x_abs, y_abs);
return addSegment(VG_MOVE_TO_ABS, {x_abs, y_abs});
}
//----------------------------------------------------------------------------
Path& Path::move(float x_rel, float y_rel)
{
return addSegment(VG_MOVE_TO_REL, x_rel, y_rel);
return addSegment(VG_MOVE_TO_REL, {x_rel, y_rel});
}
//----------------------------------------------------------------------------
Path& Path::lineTo(float x_abs, float y_abs)
{
return addSegment(VG_LINE_TO_ABS, x_abs, y_abs);
return addSegment(VG_LINE_TO_ABS, {x_abs, y_abs});
}
//----------------------------------------------------------------------------
Path& Path::line(float x_rel, float y_rel)
{
return addSegment(VG_LINE_TO_REL, x_rel, y_rel);
return addSegment(VG_LINE_TO_REL, {x_rel, y_rel});
}
//----------------------------------------------------------------------------
Path& Path::horizTo(float x_abs)
{
return addSegment(VG_HLINE_TO_ABS, x_abs);
return addSegment(VG_HLINE_TO_ABS, {x_abs});
}
//----------------------------------------------------------------------------
Path& Path::horiz(float x_rel)
{
return addSegment(VG_HLINE_TO_REL, x_rel);
return addSegment(VG_HLINE_TO_REL, {x_rel});
}
//----------------------------------------------------------------------------
Path& Path::vertTo(float y_abs)
{
return addSegment(VG_VLINE_TO_ABS, y_abs);
return addSegment(VG_VLINE_TO_ABS, {y_abs});
}
//----------------------------------------------------------------------------
Path& Path::vert(float y_rel)
{
return addSegment(VG_VLINE_TO_REL, y_rel);
return addSegment(VG_VLINE_TO_REL, {y_rel});
}
//----------------------------------------------------------------------------
@@ -828,36 +823,70 @@ namespace canvas
_node->getChild("border-radius", 1, true)->setDoubleValue(radiusY);
}
//----------------------------------------------------------------------------
void Path::updateImpl(double dt)
{
Element::updateImpl(dt);
if( _attributes_dirty & (CMDS | COORDS) )
{
_path->setSegments
(
_node->getChildValues<VGubyte, int>("cmd"),
_node->getChildValues<VGfloat, float>("coord")
);
_attributes_dirty &= ~(CMDS | COORDS);
}
// SVG path overrides manual cmd/coord specification
if( _hasSVG && (_attributes_dirty & SVG) )
{
CmdList cmds;
CoordList coords;
parseSVGPathToVGPath(_node->getStringValue("svg"), cmds, coords);
_path->setSegments(cmds, coords);
_attributes_dirty &= ~SVG;
}
if( _hasRect &&(_attributes_dirty & RECT) )
{
parseRectToVGPath();
_attributes_dirty &= ~RECT;
}
}
//----------------------------------------------------------------------------
void Path::childChanged(SGPropertyNode* child)
{
const std::string& name = child->getNameString();
const std::string &prName = child->getParent()->getNameString();
const std::string& name = child->getNameString();
const std::string &prName = child->getParent()->getNameString();
if (simgear::strutils::starts_with(name, "border-"))
{
_attributes_dirty |= RECT;
return;
}
if( strutils::starts_with(name, "border-") )
{
_attributes_dirty |= RECT;
return;
}
if (prName == "rect") {
_hasRect = true;
if (name == "left") {
_rect.setLeft(child->getDoubleValue());
} else if (name == "top") {
_rect.setTop(child->getDoubleValue());
} else if (name == "right") {
_rect.setRight(child->getDoubleValue());
} else if (name == "bottom") {
_rect.setBottom(child->getDoubleValue());
} else if (name == "width") {
_rect.setWidth(child->getDoubleValue());
} else if (name == "height") {
_rect.setHeight(child->getDoubleValue());
}
_attributes_dirty |= RECT;
return;
if (prName == "rect")
{
_hasRect = true;
if (name == "left") {
_rect.setLeft(child->getDoubleValue());
} else if (name == "top") {
_rect.setTop(child->getDoubleValue());
} else if (name == "right") {
_rect.setRight(child->getDoubleValue());
} else if (name == "bottom") {
_rect.setBottom(child->getDoubleValue());
} else if (name == "width") {
_rect.setWidth(child->getDoubleValue());
} else if (name == "height") {
_rect.setHeight(child->getDoubleValue());
}
_attributes_dirty |= RECT;
return;
}
if( child->getParent() != _node )
return;
@@ -893,67 +922,68 @@ namespace canvas
return values;
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
void operator+=(CoordList& base, const std::initializer_list<VGfloat>& other)
{
base.insert(base.end(), other.begin(), other.end());
}
void operator+=(CoordList& base, const std::initializer_list<VGfloat>& other)
{
base.insert(base.end(), other.begin(), other.end());
//----------------------------------------------------------------------------
void Path::parseRectToVGPath()
{
CmdList commands;
CoordList coords;
commands.reserve(4);
coords.reserve(8);
bool haveCorner = false;
SGVec2f topLeft = parseRectCornerRadius(_node, "left", "top", haveCorner);
if (haveCorner) {
commands.push_back(VG_MOVE_TO_ABS);
coords += {_rect.l(), _rect.t() + topLeft.y()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {topLeft.x(), topLeft.y(), 0.0, topLeft.x(), -topLeft.y()};
} else {
commands.push_back(VG_MOVE_TO_ABS);
coords += {_rect.l(), _rect.t()};
}
void Path::parseRectToVGPath()
{
CmdList commands;
CoordList coords;
commands.reserve(4);
coords.reserve(8);
bool haveCorner = false;
SGVec2f topLeft = parseRectCornerRadius(_node, "left", "top", haveCorner);
if (haveCorner) {
commands.push_back(VG_MOVE_TO_ABS);
coords += {_rect.l(), _rect.t() + topLeft.y()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {topLeft.x(), topLeft.y(), 0.0, topLeft.x(), -topLeft.y()};
} else {
commands.push_back(VG_MOVE_TO_ABS);
coords += {_rect.l(), _rect.t()};
}
SGVec2f topRight = parseRectCornerRadius(_node, "right", "top", haveCorner);
if (haveCorner) {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.r() - topRight.x()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {topRight.x(), topRight.y(), 0.0, topRight.x(), topRight.y()};
} else {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.r()};
}
SGVec2f bottomRight = parseRectCornerRadius(_node, "right", "bottom", haveCorner);
if (haveCorner) {
commands.push_back(VG_VLINE_TO_ABS);
coords += {_rect.b() - bottomRight.y()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {bottomRight.x(), bottomRight.y(), 0.0, -bottomRight.x(), bottomRight.y()};
} else {
commands.push_back(VG_VLINE_TO_ABS);
coords += {_rect.b()};
}
SGVec2f bottomLeft = parseRectCornerRadius(_node, "left", "bottom", haveCorner);
if (haveCorner) {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.l() + bottomLeft.x()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {bottomLeft.x(), bottomLeft.y(), 0.0, -bottomLeft.x(), -bottomLeft.y()};
} else {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.l()};
}
commands.push_back(VG_CLOSE_PATH);
_path->setSegments(commands, coords);
SGVec2f topRight = parseRectCornerRadius(_node, "right", "top", haveCorner);
if (haveCorner) {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.r() - topRight.x()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {topRight.x(), topRight.y(), 0.0, topRight.x(), topRight.y()};
} else {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.r()};
}
SGVec2f bottomRight = parseRectCornerRadius(_node, "right", "bottom", haveCorner);
if (haveCorner) {
commands.push_back(VG_VLINE_TO_ABS);
coords += {_rect.b() - bottomRight.y()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {bottomRight.x(), bottomRight.y(), 0.0, -bottomRight.x(), bottomRight.y()};
} else {
commands.push_back(VG_VLINE_TO_ABS);
coords += {_rect.b()};
}
SGVec2f bottomLeft = parseRectCornerRadius(_node, "left", "bottom", haveCorner);
if (haveCorner) {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.l() + bottomLeft.x()};
commands.push_back(VG_SCCWARC_TO_REL);
coords += {bottomLeft.x(), bottomLeft.y(), 0.0, -bottomLeft.x(), -bottomLeft.y()};
} else {
commands.push_back(VG_HLINE_TO_ABS);
coords += {_rect.l()};
}
commands.push_back(VG_CLOSE_PATH);
_path->setSegments(commands, coords);
}
} // namespace canvas
} // namespace simgear

View File

@@ -20,8 +20,8 @@
#define CANVAS_PATH_HXX_
#include "CanvasElement.hxx"
#include <boost/preprocessor/iteration/iterate.hpp>
#include <simgear/math/SGRect.hxx>
#include <initializer_list>
namespace simgear
{
@@ -40,14 +40,10 @@ namespace canvas
ElementWeakPtr parent = 0 );
virtual ~Path();
virtual void update(double dt);
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
#define BOOST_PP_ITERATION_LIMITS (0, 6)
#define BOOST_PP_FILENAME_1 \
<simgear/canvas/elements/detail/add_segment_variadic.hxx>
#include BOOST_PP_ITERATE()
/** Add a segment with the given command and coordinates */
Path& addSegment(uint8_t cmd, std::initializer_list<float> coords = {});
/** Move path cursor */
Path& moveTo(float x_abs, float y_abs);
@@ -70,10 +66,10 @@ namespace canvas
void setSVGPath(const std::string& svgPath);
void setRect(const SGRect<float>& r);
void setRoundRect(const SGRect<float>& r, float radiusX, float radiusY = -1.0);
protected:
void setRect(const SGRectf& r);
void setRoundRect(const SGRectf& r, float radiusX, float radiusY = -1.0);
protected:
enum PathAttributes
{
CMDS = LAST_ATTRIBUTE << 1,
@@ -88,12 +84,14 @@ namespace canvas
bool _hasSVG : 1;
bool _hasRect : 1;
SGRect<float> _rect;
SGRectf _rect;
virtual void updateImpl(double dt);
void parseRectToVGPath();
virtual void childRemoved(SGPropertyNode * child);
virtual void childChanged(SGPropertyNode * child);
void parseRectToVGPath();
};
} // namespace canvas

View File

@@ -865,12 +865,12 @@ namespace canvas
//----------------------------------------------------------------------------
osg::StateSet* Text::getOrCreateStateSet()
{
if( !_transform.valid() )
return 0;
if( !_scene_group.valid() )
return nullptr;
// Only check for StateSet on Transform, as the text stateset is shared
// between all text instances using the same font (texture).
return _transform->getOrCreateStateSet();
return _scene_group->getOrCreateStateSet();
}
} // namespace canvas

View File

@@ -1,21 +0,0 @@
#ifndef CANVAS_PATH_HXX_
# error Canvas - do not include this file!
#endif
#define n BOOST_PP_ITERATION()
Path& addSegment( uint8_t cmd
BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM_PARAMS(n, float coord) )
{
_node->addChild("cmd")->setIntValue(cmd);
#define SG_CANVAS_PATH_SET_COORD(z, n, dummy)\
_node->addChild("coord")->setFloatValue(coord##n);
BOOST_PP_REPEAT(n, SG_CANVAS_PATH_SET_COORD, 0)
#undef SG_CANVAS_PATH_SET_COORD
return *this;
}
#undef n

View File

@@ -190,6 +190,32 @@ namespace canvas
}
};
/**
* WebMercator projection, relative to the projection center.
* Required for Slippy Maps - i.e. openstreetmap
*/
class WebMercatorProjection:
public HorizontalProjection
{
protected:
virtual ScreenPosition project(double lat, double lon) const
{
double d_lat = lat - _ref_lat,
d_lon = lon - _ref_lon;
double r = 6378137.f / 1852; // Equatorial radius divided by ?
ScreenPosition pos;
pos.x = r * d_lon;
pos.y = r * (log(tan(d_lat) + 1.0 / cos(d_lat)));
//pos.x = lon;
//pos.y = log(tan(lat) + 1.0 / cos(lat));
return pos;
}
};
} // namespace canvas
} // namespace simgear

View File

@@ -19,7 +19,6 @@
#include <simgear_config.h>
#include "KeyboardEvent.hxx"
#include "utf8.h"
#include <osgGA/GUIEventAdapter>
@@ -42,6 +41,7 @@ namespace canvas
// TODO check Win/Mac keycode for altgr/ISO Level3 Shift
const uint32_t KEY_AltGraph = 0xfe03;
//----------------------------------------------------------------------------
KeyboardEvent::KeyboardEvent():
@@ -269,10 +269,24 @@ namespace canvas
// Empty or no mapping -> convert UTF-32 key value to UTF-8
if( _name.empty() )
{
if( !utf8::internal::is_code_point_valid(_key) )
if (( _key >= 0xd800u && _key <= 0xdfffu ) || _key > 0x10ffffu )
_name = "Unidentified";
else
utf8::unchecked::append(_key, std::back_inserter(_name));
if ( _key <= 0x7f ) {
_name.push_back(static_cast<uint8_t>(_key));
} else if ( _key <= 0x7ff ) {
_name.push_back(static_cast<uint8_t>((_key >> 6) | 0xc0));
_name.push_back(static_cast<uint8_t>((_key & 0x3f) | 0x80));
} else if ( _key <= 0xffff ) {
_name.push_back(static_cast<uint8_t>((_key >> 12) | 0xe0));
_name.push_back(static_cast<uint8_t>(((_key >> 6) & 0x3f) | 0x80));
_name.push_back(static_cast<uint8_t>((_key & 0x3f) | 0x80));
} else {
_name.push_back(static_cast<uint8_t>((_key >> 18) | 0xf0));
_name.push_back(static_cast<uint8_t>(((_key >> 12) & 0x3f) | 0x80));
_name.push_back(static_cast<uint8_t>(((_key >> 6) & 0x3f) | 0x80));
_name.push_back(static_cast<uint8_t>((_key & 0x3f) | 0x80));
}
}
// Keys on the numpad with NumLock enabled are reported just like their
@@ -307,11 +321,30 @@ namespace canvas
if( key_name.empty() )
return false;
std::string::const_iterator it = key_name.begin();
uint32_t cp = utf8::next(it, key_name.end());
// Convert the key name to the corresponding code point by checking the
// sequence length (the first bits of the first byte) and performing the
// conversion accordingly.
uint32_t cp = key_name[0] & 0xff;
size_t len;
if (cp < 0x80) {
len = 1;
} else if ((cp >> 5) == 0x6) {
cp = ((cp << 6) & 0x7ff) + (key_name[1] & 0x3f);
len = 2;
} else if ((cp >> 4) == 0xe) {
cp = ((cp << 12) & 0xffff) + (((key_name[1] & 0xff) << 6) & 0xfff)
+ (key_name[2] & 0x3f);
len = 3;
} else if ((cp >> 3) == 0x1e) {
cp = ((cp << 18) & 0x1fffff) + (((key_name[1] & 0xff) << 12) & 0x3ffff)
+ (((key_name[2] & 0xff) << 6) & 0xfff) + (key_name[3] & 0x3f);
len = 4;
} else {
return false;
}
// Check if _name contains exactly one (UTF-8 encoded) character.
if( it != key_name.end() )
if (key_name.length() > len)
return false;
// C0 and C1 control characters are not printable.

View File

@@ -45,7 +45,7 @@ namespace canvas
//----------------------------------------------------------------------------
NasalWidget::~NasalWidget()
{
onRemove();
}
//----------------------------------------------------------------------------
@@ -189,10 +189,10 @@ namespace canvas
if( hfw.empty() )
return -1;
naContext c = naNewContext();
try
{
return hfw(nasal::to_nasal(c, const_cast<NasalWidget*>(this)), w);
nasal::Context ctx;
return hfw(ctx.to_me(const_cast<NasalWidget*>(this)), w);
}
catch( std::exception const& ex )
{
@@ -202,7 +202,6 @@ namespace canvas
"NasalWidget.heightForWidth: callback error: '" << ex.what() << "'"
);
}
naFreeContext(c);
return -1;
}
@@ -262,8 +261,8 @@ namespace canvas
try
{
nasal::Context c;
_set_geometry(nasal::to_nasal(c, this), rect);
nasal::Context ctx;
_set_geometry(ctx.to_me(this), rect);
_flags &= ~LAYOUT_DIRTY;
}
catch( std::exception const& ex )

View File

@@ -95,7 +95,7 @@ const float SG_RADIANS_TO_DEGREES = 180.0f / SG_PI;
/** Value of earth radius from LaRCsim (ft) */
#define SG_EQUATORIAL_RADIUS_FT 20925650.
/** Value of earth radius from LaRCsim (meter) */
/** Value of equatorial earth radius from LaRCsim (meter) */
#define SG_EQUATORIAL_RADIUS_M 6378138.12
/** Radius squared (ft) */
@@ -104,6 +104,8 @@ const float SG_RADIANS_TO_DEGREES = 180.0f / SG_PI;
/** Radius squared (meter) */
#define SG_EQ_RAD_SQUARE_M 40680645877797.1344
/** Value of WGS84 polar earth radius (meter) */
#define SG_POLAR_RADIUS_M 6356752.3142451794975639668
// Physical Constants, SI

View File

@@ -531,7 +531,7 @@ logstream::log( sgDebugClass c, sgDebugPriority p,
}
void logstream::hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName, int line, const void *mem, unsigned int len, int columns)
void logstream::hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName, int line, const void *mem, unsigned int len, unsigned int columns)
{
unsigned int i, j;
char temp[3000], temp1[3000];

View File

@@ -120,7 +120,7 @@ public:
/**
* 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);
void hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName, int line, const void *mem, unsigned int len, unsigned int columns = 16);
/**

View File

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

View File

@@ -0,0 +1,265 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResource.cxx --- Class for pointing to/accessing an embedded resource
// Copyright (C) 2017 Florent Rougon
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#include <simgear_config.h>
#include <string>
#include <iosfwd>
#include <ios> // std::streamsize
#include <ostream>
#include <memory> // std::unique_ptr
#include <utility> // std::move()
#include <algorithm> // std::min()
#include <limits> // std::numeric_limits
#include <cstddef> // std::size_t, std::ptrdiff_t
#include <simgear/structure/exception.hxx>
#include <simgear/io/iostreams/CharArrayStream.hxx>
#include <simgear/io/iostreams/zlibstream.hxx>
#include "EmbeddedResource.hxx"
using std::string;
using std::unique_ptr;
// Inspired by <http://stackoverflow.com/a/21174979/4756009>
template<typename Derived, typename Base>
static unique_ptr<Derived> static_unique_ptr_cast(unique_ptr<Base> p)
{
auto d = static_cast<Derived *>(p.release());
return unique_ptr<Derived>(d);
}
namespace simgear
{
// ***************************************************************************
// * AbstractEmbeddedResource class *
// ***************************************************************************
AbstractEmbeddedResource::AbstractEmbeddedResource(const char *data,
std::size_t size)
: _data(data),
_size(size)
{ }
const char *AbstractEmbeddedResource::rawPtr() const
{
return _data;
}
std::size_t AbstractEmbeddedResource::rawSize() const
{
return _size;
}
string AbstractEmbeddedResource::str() const
{
if (_size > std::numeric_limits<string::size_type>::max()) {
throw sg_range_exception(
"Resource too large to fit in an std::string (size: " +
std::to_string(_size) + " bytes)");
}
return string(_data, _size);
}
// ***************************************************************************
// * RawEmbeddedResource class *
// ***************************************************************************
RawEmbeddedResource::RawEmbeddedResource(const char *data, std::size_t size)
: AbstractEmbeddedResource(data, size)
{ }
AbstractEmbeddedResource::CompressionType
RawEmbeddedResource::compressionType() const
{
return AbstractEmbeddedResource::CompressionType::NONE;
}
string RawEmbeddedResource::compressionDescr() const
{
return string("none");
}
unique_ptr<std::streambuf> RawEmbeddedResource::streambuf() const
{
// This is a read-only variant of CharArrayStreambuf
return unique_ptr<std::streambuf>(
new ROCharArrayStreambuf(rawPtr(), rawSize()));
}
unique_ptr<std::istream> RawEmbeddedResource::istream() const
{
return unique_ptr<std::istream>(new CharArrayIStream(rawPtr(), rawSize()));
}
// ***************************************************************************
// * ZlibEmbeddedResource class *
// ***************************************************************************
ZlibEmbeddedResource::ZlibEmbeddedResource(const char *data,
std::size_t compressedSize,
std::size_t uncompressedSize)
: AbstractEmbeddedResource(data, compressedSize),
_uncompressedSize(uncompressedSize),
_inBuf(nullptr),
_inBufSize(262144), // adjusted below in the constructor body
_outBuf(nullptr),
_outBufSize(262144),
_putbackSize(0) // default for best performance
{
static_assert(262144 <= std::numeric_limits<std::size_t>::max(),
"The std::size_t type is unexpectedly small.");
// No need to use an input buffer (where compressed data chunks are put for
// zlib to read and decompress) larger than the whole compressed resource!
_inBufSize = std::min(rawSize(), _inBufSize);
}
AbstractEmbeddedResource::CompressionType
ZlibEmbeddedResource::compressionType() const
{ return AbstractEmbeddedResource::CompressionType::ZLIB; }
string ZlibEmbeddedResource::compressionDescr() const
{ return string("zlib"); }
std::size_t ZlibEmbeddedResource::uncompressedSize() const
{ return _uncompressedSize; }
char* ZlibEmbeddedResource::getInputBufferStart()
{ return _inBuf; }
void ZlibEmbeddedResource::setInputBufferStart(char* inBuf)
{ _inBuf = inBuf; }
std::size_t ZlibEmbeddedResource::getInputBufferSize()
{ return _inBufSize; }
void ZlibEmbeddedResource::setInputBufferSize(std::size_t size)
{ _inBufSize = size; }
char* ZlibEmbeddedResource::getOutputBufferStart()
{ return _outBuf; }
void ZlibEmbeddedResource::setOutputBufferStart(char* outBuf)
{ _outBuf = outBuf; }
std::size_t ZlibEmbeddedResource::getOutputBufferSize()
{ return _outBufSize; }
void ZlibEmbeddedResource::setOutputBufferSize(std::size_t size)
{ _outBufSize = size; }
std::size_t ZlibEmbeddedResource::getPutbackSize()
{ return _putbackSize; }
void ZlibEmbeddedResource::setPutbackSize(std::size_t size)
{ _putbackSize = size; }
unique_ptr<std::streambuf> ZlibEmbeddedResource::streambuf() const
{
unique_ptr<CharArrayIStream> rawReaderIStream(
new CharArrayIStream(rawPtr(), rawSize()));
return unique_ptr<std::streambuf>(
new ZlibDecompressorIStreambuf(
std::move(rawReaderIStream),
SGPath(), // rawReaderIStream isn't bound to a file
ZLibCompressionFormat::ZLIB,
_inBuf, _inBufSize, _outBuf, _outBufSize, _putbackSize));
}
unique_ptr<std::istream> ZlibEmbeddedResource::istream() const
{
unique_ptr<CharArrayIStream> rawReaderIStream(
new CharArrayIStream(rawPtr(), rawSize()));
return unique_ptr<std::istream>(
new ZlibDecompressorIStream(
std::move(rawReaderIStream),
SGPath(), // rawReaderIStream isn't bound to a file
ZLibCompressionFormat::ZLIB,
_inBuf, _inBufSize, _outBuf, _outBufSize, _putbackSize));
}
std::string ZlibEmbeddedResource::str() const
{
static constexpr std::size_t bufSize = 65536;
static_assert(bufSize <= std::numeric_limits<std::streamsize>::max(),
"Type std::streamsize is unexpectedly small");
static_assert(bufSize <= std::numeric_limits<string::size_type>::max(),
"Type std::string::size_type is unexpectedly small");
unique_ptr<char[]> buf(new char[bufSize]);
auto decompressor =
static_unique_ptr_cast<ZlibDecompressorIStream>(istream());
std::streamsize nbCharsRead;
string result;
if (_uncompressedSize > std::numeric_limits<string::size_type>::max()) {
throw sg_range_exception(
"Resource too large to fit in an std::string (uncompressed size: "
+ std::to_string(_uncompressedSize) + " bytes)");
} else {
result.reserve(static_cast<string::size_type>(_uncompressedSize));
}
do {
decompressor->read(buf.get(), bufSize);
nbCharsRead = decompressor->gcount();
if (nbCharsRead > 0) {
result.append(buf.get(), nbCharsRead);
}
} while (*decompressor);
// decompressor->fail() would *not* indicate an error, due to the semantics
// of std::istream::read().
if (decompressor->bad()) {
throw sg_io_exception("Error while extracting a compressed resource");
}
return result;
}
// ***************************************************************************
// * Stream insertion operators *
// ***************************************************************************
std::ostream& operator<<(std::ostream& os,
const RawEmbeddedResource& resource)
{ // This won't escape double quotes, backslashes, etc. in resource.str().
return os << "RawEmbeddedResource:\n"
" compressionType = \"" << resource.compressionDescr() << "\"\n"
" rawPtr = " << (void*) resource.rawPtr() << "\n"
" rawSize = " << resource.rawSize();
}
std::ostream& operator<<(std::ostream& os,
const ZlibEmbeddedResource& resource)
{ // This won't escape double quotes, backslashes, etc. in resource.str().
return os << "ZlibEmbeddedResource:\n"
" compressionType = \"" << resource.compressionDescr() << "\"\n"
" rawPtr = " << (void*) resource.rawPtr() << "\n"
" rawSize = " << resource.rawSize() << "\n"
" uncompressedSize = " << resource.uncompressedSize();
}
} // of namespace simgear

View File

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

View File

@@ -0,0 +1,235 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResourceManager.cxx --- Manager class for resources embedded in an
// executable
// Copyright (C) 2017 Florent Rougon
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#include <simgear_config.h>
#include <memory>
#include <utility> // std::move()
#include <string>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <simgear/structure/exception.hxx>
#include "EmbeddedResource.hxx"
#include "EmbeddedResourceManager.hxx"
#include "EmbeddedResourceManager_private.hxx"
using std::string;
using std::shared_ptr;
using std::unique_ptr;
namespace simgear
{
static unique_ptr<EmbeddedResourceManager> staticInstance;
// ***************************************************************************
// * EmbeddedResourceManager::Impl *
// ***************************************************************************
EmbeddedResourceManager::Impl::Impl()
: dirty(true)
{ }
void
EmbeddedResourceManager::Impl::rehash()
{
// Update the list of resource pools to search when looking up a resource.
// This allows to optimize resource lookup: no need to parse, split and hash
// the same locale string every time to find the corresponding resource
// pools.
poolSearchList = listOfResourcePoolsToSearch(selectedLocale);
dirty = false;
}
string
EmbeddedResourceManager::Impl::getLocale() const
{
return selectedLocale;
}
string
EmbeddedResourceManager::Impl::selectLocale(const std::string& locale)
{
string previousLocale = std::move(selectedLocale);
selectedLocale = locale;
dirty = true;
return previousLocale;
}
// Static method
std::vector<string>
EmbeddedResourceManager::Impl::localesSearchList(const string& locale)
{
std::vector<string> result;
if (locale.empty()) {
result.push_back(string()); // only the default locale
} else {
std::size_t sepIdx = locale.find_first_of('_');
if (sepIdx == string::npos) {
// Try the given “locale” first (e.g., fr), then the default locale
result = std::vector<string>({locale, string()});
} else {
string langCode = locale.substr(0, sepIdx);
// Try the given “locale” first (e.g., fr_FR), then the language code
// (e.g., fr) and finally the default locale
result = std::vector<string>({locale, langCode, string()});
}
}
return result;
}
auto
EmbeddedResourceManager::Impl::listOfResourcePoolsToSearch(
const string& locale) const
-> std::vector< shared_ptr<ResourcePool> >
{
std::vector<string> searchedLocales = localesSearchList(locale);
std::vector< shared_ptr<ResourcePool> > result;
for (const string& loc: searchedLocales) {
auto poolPtrIt = localeToResourcePoolMap.find(loc);
// Don't store pointers to empty resource pools in 'result'. This
// optimizes resource fetching a little bit, but requires that all
// resources are added before this method is called.
if (poolPtrIt != localeToResourcePoolMap.end()) {
// Copy a shared_ptr<ResourcePool>
result.push_back(poolPtrIt->second);
}
}
return result;
}
// Static method
shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::Impl::lookupResourceInPools(
const string& virtualPath,
const std::vector< shared_ptr<ResourcePool> >& aPoolSearchList)
{
// Search the provided resource pools in proper order. For instance, the one
// for 'fr_FR', then the one for 'fr' and finally the one for the default
// locale. Return the first resource found in one of these pools.
for (const shared_ptr<ResourcePool>& poolPtr: aPoolSearchList) {
auto resourcePtrIt = poolPtr->find(virtualPath);
if (resourcePtrIt != poolPtr->end()) {
// Copy a shared_ptr<const AbstractEmbeddedResource>
return resourcePtrIt->second;
}
}
return shared_ptr<const AbstractEmbeddedResource>(); // null shared_ptr object
}
void
EmbeddedResourceManager::Impl::addResource(
const string& virtualPath,
unique_ptr<const AbstractEmbeddedResource> resourcePtr,
const string& locale)
{
// Find the resource pool corresponding to the specified locale
shared_ptr<ResourcePool>& resPoolPtr = localeToResourcePoolMap[locale];
if (!resPoolPtr) {
resPoolPtr.reset(new ResourcePool());
}
auto emplaceRetval = resPoolPtr->emplace(virtualPath, std::move(resourcePtr));
if (!emplaceRetval.second) {
const string localeDescr =
(locale.empty()) ? "the default locale" : "locale '" + locale + "'";
throw sg_error(
"Virtual path already in use for " + localeDescr +
" in the EmbeddedResourceManager: '" + virtualPath + "'");
}
dirty = true;
}
// ***************************************************************************
// * EmbeddedResourceManager *
// ***************************************************************************
EmbeddedResourceManager::EmbeddedResourceManager()
: p(unique_ptr<Impl>(new Impl))
{ }
const unique_ptr<EmbeddedResourceManager>&
EmbeddedResourceManager::createInstance()
{
staticInstance.reset(new EmbeddedResourceManager);
return staticInstance;
}
const unique_ptr<EmbeddedResourceManager>&
EmbeddedResourceManager::instance()
{
return staticInstance;
}
string
EmbeddedResourceManager::getLocale() const
{
return p->getLocale();
}
string
EmbeddedResourceManager::selectLocale(const std::string& locale)
{
return p->selectLocale(locale);
}
void
EmbeddedResourceManager::addResource(
const string& virtualPath,
unique_ptr<const AbstractEmbeddedResource> resourcePtr,
const string& locale)
{
p->addResource(virtualPath, std::move(resourcePtr), locale);
}
shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::getResourceOrNullPtr(const string& virtualPath) const
{
if (p->dirty) {
p->rehash(); // update p->poolSearchList
}
// Use the selected locale
return p->lookupResourceInPools(virtualPath, p->poolSearchList);
}
shared_ptr<const AbstractEmbeddedResource>
EmbeddedResourceManager::getResourceOrNullPtr(const string& virtualPath,
const string& locale) const
{
// In this overload, we don't use the cached list of pools
// (p->poolSearchList), therefore there is no need to check the 'dirty' flag
// or to rehash().
return p->lookupResourceInPools(virtualPath,
p->listOfResourcePoolsToSearch(locale));
}
} // of namespace simgear

View File

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

View File

@@ -0,0 +1,102 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResourceManager_private.hxx --- Private implementation class for
// SimGear's EmbeddedResourceManager
// Copyright (C) 2017 Florent Rougon
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#ifndef FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
#define FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX
#include <string>
#include <memory> // std::unique_ptr, std::shared_ptr
#include <unordered_map>
#include <vector>
#include "EmbeddedResource.hxx"
namespace simgear
{
class EmbeddedResourceManager::Impl
{
public:
explicit Impl();
// Each “locale” for which addResource() has been used has an associated
// resource pool, that is a sort of directory of all resources declared in
// this locale. The resource pool for a given locale (e.g., 'fr' or 'de_DE')
// maps resource virtual paths to the corresponding resource descriptors
// (via std::shared_ptr<const AbstractEmbeddedResource> instances).
//
// Note: for optimal lookup performance, a tree would probably be better,
// since the expected use for each key here is to store a virtual
// path. But such an optimization is likely unneeded in most cases.
typedef std::unordered_map< std::string,
std::shared_ptr<const AbstractEmbeddedResource> >
ResourcePool;
// Return the list of “locales” to scan to implement fallback behavior when
// fetching a resource for the specified locale. This list will be searched
// from left to right. Examples:
//
// "" -> [""]
// "fr" -> ["fr", ""]
// "fr_FR" -> ["fr_FR", "fr", ""]
static std::vector<std::string> localesSearchList(const std::string& locale);
// Same as localesSearchList(), except it returns the resource pools instead
// of the “locale” strings, and only those pools that are not empty.
std::vector< std::shared_ptr<ResourcePool> > listOfResourcePoolsToSearch(
const std::string& locale) const;
// Look up, in each of the pools referred to by 'poolSearchList', the
// resource associated to 'virtualPath'. Return the first match.
static std::shared_ptr<const AbstractEmbeddedResource> lookupResourceInPools(
const std::string& virtualPath,
const std::vector< std::shared_ptr<ResourcePool> >& poolSearchList);
// Recompute p->poolSearchList. This method is automatically called whenever
// needed (lazily), so it doesn't need to be part of the public interface.
void rehash();
// Implement the corresponding EmbeddedResourceManager public methods
std::string getLocale() const;
std::string selectLocale(const std::string& locale);
// Ditto
void addResource(const std::string& virtualPath,
std::unique_ptr<const AbstractEmbeddedResource> resourcePtr,
const std::string& locale);
std::string selectedLocale;
// Each call to rehash() updates this member to contain precisely the
// (ordered) list of pools to search for a resource in the selected
// “locale”. This allows relatively cheap resource lookups, assuming the
// desired “locale” doesn't change all the time.
std::vector< std::shared_ptr<ResourcePool> > poolSearchList;
// Indicate whether 'poolSearchList' must be updated (i.e., resources have
// been added or the selected locale was changed without rehash() being
// called afterwards).
bool dirty;
// Maps each “locale name” to the corresponding resource pool.
std::unordered_map< std::string,
std::shared_ptr<ResourcePool> > localeToResourcePoolMap;
};
} // of namespace simgear
#endif // of FG_EMBEDDEDRESOURCEMANAGERPRIVATE_HXX

View File

@@ -0,0 +1,257 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResourceProxy.cxx --- Unified access to real files or embedded
// resources
// 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 <algorithm> // std::find()
#include <ios> // std::streamsize
#include <istream>
#include <limits> // std::numeric_limits
#include <memory>
#include <vector>
#include <string>
#include <cstdlib> // std::size_t
#include <cassert>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/structure/exception.hxx>
#include "EmbeddedResourceManager.hxx"
#include "EmbeddedResourceProxy.hxx"
using std::string;
using std::vector;
using std::shared_ptr;
using std::unique_ptr;
namespace simgear
{
EmbeddedResourceProxy::EmbeddedResourceProxy(const SGPath& realRoot,
const string& virtualRoot,
bool useEmbeddedResourcesByDefault)
: _realRoot(realRoot),
_virtualRoot(normalizeVirtualRoot(virtualRoot)),
_useEmbeddedResourcesByDefault(useEmbeddedResourcesByDefault)
{ }
SGPath
EmbeddedResourceProxy::getRealRoot() const
{ return _realRoot; }
void
EmbeddedResourceProxy::setRealRoot(const SGPath& realRoot)
{ _realRoot = realRoot; }
string
EmbeddedResourceProxy::getVirtualRoot() const
{ return _virtualRoot; }
void
EmbeddedResourceProxy::setVirtualRoot(const string& virtualRoot)
{ _virtualRoot = normalizeVirtualRoot(virtualRoot); }
bool
EmbeddedResourceProxy::getUseEmbeddedResources() const
{ return _useEmbeddedResourcesByDefault; }
void
EmbeddedResourceProxy::setUseEmbeddedResources(bool useEmbeddedResources)
{ _useEmbeddedResourcesByDefault = useEmbeddedResources; }
// Static method: normalize the 'virtualRoot' argument of the constructor
//
// The argument must start with a slash and mustn't contain any '.' or '..'
// component. The return value never ends with a slash.
string
EmbeddedResourceProxy::normalizeVirtualRoot(const string& path)
{
EmbeddedResourceProxy::checkPath(__func__, path,
false /* allowStartWithColon */);
string res = path;
// Make sure 'res' doesn't end with a '/'.
while (!res.empty() && res.back() == '/') {
res.pop_back(); // This will ease path concatenation
}
return res;
}
// Static method
void
EmbeddedResourceProxy::checkPath(const string& callerMethod, const string& path,
bool allowStartWithColon)
{
if (path.empty()) {
throw sg_format_exception(
"Invalid empty path for EmbeddedResourceProxy::" +
callerMethod + "(): '" + path + "'", path);
} else if (allowStartWithColon &&
!simgear::strutils::starts_with(path, ":/") && path[0] != '/') {
throw sg_format_exception(
"Invalid path for EmbeddedResourceProxy::" + callerMethod + "(): "
"it should start with either ':/' or '/'", path);
} else if (!allowStartWithColon && path[0] != '/') {
throw sg_format_exception(
"Invalid path for EmbeddedResourceProxy::" + callerMethod + "(): "
"it should start with a slash ('/')", path);
} else {
const vector<string> components = simgear::strutils::split(path, "/");
auto find = [&components](const string& s) -> bool {
return (std::find(components.begin(), components.end(), s) !=
components.end());
};
if (find(".") || find("..")) {
throw sg_format_exception(
"Invalid path for EmbeddedResourceProxy::" + callerMethod + "(): "
"'.' and '..' components are not allowed", path);
}
}
}
unique_ptr<std::istream>
EmbeddedResourceProxy::getIStream(const string& path, bool fromEmbeddedResource)
const
{
EmbeddedResourceProxy::checkPath(__func__, path,
false /* allowStartWithColon */);
assert(!path.empty() && path.front() == '/');
if (fromEmbeddedResource) {
const auto& embeddedResMgr = simgear::EmbeddedResourceManager::instance();
return embeddedResMgr->getIStream(
_virtualRoot + path,
""); // fetch the default-locale version of the resource
} else {
const SGPath sgPath = _realRoot / path.substr(std::size_t(1));
return unique_ptr<std::istream>(new sg_ifstream(sgPath));
}
}
unique_ptr<std::istream>
EmbeddedResourceProxy::getIStream(const string& path) const
{
return getIStream(path, _useEmbeddedResourcesByDefault);
}
unique_ptr<std::istream>
EmbeddedResourceProxy::getIStreamDecideOnPrefix(const string& path) const
{
EmbeddedResourceProxy::checkPath(__func__, path,
true /* allowStartWithColon */);
// 'path' is non-empty
if (path.front() == '/') {
return getIStream(path, false /* fromEmbeddedResource */);
} else if (path.front() == ':') {
assert(path.size() >= 2 && path[1] == '/');
// Skip the leading ':'
return getIStream(path.substr(std::size_t(1)),
true /* fromEmbeddedResource */);
} else {
// The checkPath() call should make it impossible to reach this point.
std::abort();
}
}
string
EmbeddedResourceProxy::getString(const string& path, bool fromEmbeddedResource)
const
{
string result;
EmbeddedResourceProxy::checkPath(__func__, path,
false /* allowStartWithColon */);
assert(!path.empty() && path.front() == '/');
if (fromEmbeddedResource) {
const auto& embeddedResMgr = simgear::EmbeddedResourceManager::instance();
// Fetch the default-locale version of the resource
result = embeddedResMgr->getString(_virtualRoot + path, "");
} else {
const SGPath sgPath = _realRoot / path.substr(std::size_t(1));
result.reserve(sgPath.sizeInBytes());
const unique_ptr<std::istream> streamp = getIStream(path,
fromEmbeddedResource);
std::streamsize nbCharsRead;
// Allocate a buffer
static constexpr std::size_t bufSize = 65536;
static_assert(bufSize <= std::numeric_limits<std::streamsize>::max(),
"Type std::streamsize is unexpectedly small");
static_assert(bufSize <= std::numeric_limits<string::size_type>::max(),
"Type std::string::size_type is unexpectedly small");
unique_ptr<char[]> buf(new char[bufSize]);
do {
streamp->read(buf.get(), bufSize);
nbCharsRead = streamp->gcount();
if (nbCharsRead > 0) {
result.append(buf.get(), nbCharsRead);
}
} while (*streamp);
// streamp->fail() would *not* indicate an error, due to the semantics
// of std::istream::read().
if (streamp->bad()) {
throw sg_io_exception("Error reading from file", sg_location(path));
}
}
return result;
}
string
EmbeddedResourceProxy::getString(const string& path) const
{
return getString(path, _useEmbeddedResourcesByDefault);
}
string
EmbeddedResourceProxy::getStringDecideOnPrefix(const string& path) const
{
string result;
EmbeddedResourceProxy::checkPath(__func__, path,
true /* allowStartWithColon */);
// 'path' is non-empty
if (path.front() == '/') {
result = getString(path, false /* fromEmbeddedResource */);
} else if (path.front() == ':') {
assert(path.size() >= 2 && path[1] == '/');
// Skip the leading ':'
result = getString(path.substr(std::size_t(1)),
true /* fromEmbeddedResource */);
} else {
// The checkPath() call should make it impossible to reach this point.
std::abort();
}
return result;
}
} // of namespace simgear

View File

@@ -0,0 +1,155 @@
// -*- coding: utf-8 -*-
//
// EmbeddedResourceProxy.hxx --- Unified access to real files or embedded
// resources
// Copyright (C) 2017 Florent Rougon
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301 USA.
#ifndef FG_EMBEDDEDRESOURCEPROXY_HXX
#define FG_EMBEDDEDRESOURCEPROXY_HXX
#include <istream>
#include <memory>
#include <string>
#include <simgear/misc/sg_path.hxx>
// The EmbeddedResourceProxy class allows one to access real files or embedded
// resources in a unified way. When using it, one can switch from one data
// source to the other with minimal code changes, possibly even at runtime (in
// which case there is obviously no code change at all).
//
// Sample usage of the EmbeddedResourceProxy class (from FlightGear):
//
// simgear::EmbeddedResourceProxy proxy(globals->get_fg_root(), "/FGData");
// std::string s = proxy.getString("/some/path");
// std::unique_ptr<std::istream> streamp = proxy.getIStream("/some/path");
//
// The methods getString(const std::string& path) and
// getIStream(const std::string& path) of EmbeddedResourceProxy decide whether
// to use embedded resources or real files depending on the boolean value
// passed to EmbeddedResourceProxy::setUseEmbeddedResources() (also available
// as an optional parameter to the EmbeddedResourceProxy constructor,
// defaulting to true). It is often most convenient to set this boolean once
// and then don't worry about it anymore (it is stored as a data member of
// EmbeddedResourceProxy). Otherwise, if you want to fetch resources some
// times from real files, other times from embedded resources, you may use the
// following methods:
//
// // Retrieve contents using embedded resources
// std:string s = proxy.getString("/some/path", true);
// std:string s = proxy.getStringDecideOnPrefix(":/some/path");
//
// // Retrieve contents using real files
// std:string s = proxy.getString("/some/path", false);
// std:string s = proxy.getStringDecideOnPrefix("/some/path");
//
// You can do exactly the same with EmbeddedResourceProxy::getIStream() and
// EmbeddedResourceProxy::getIStreamDecideOnPrefix(), except they return an
// std::unique_ptr<std::istream> instead of an std::string.
//
// Given how the 'proxy' object was constructed above, each of these calls
// will fetch data from either the real file $FG_ROOT/some/path or the
// embedded resource whose virtual path is '/FGData/some/path' (more
// precisely: the default-locale version of this resource).
//
// The 'path' argument of EmbeddedResourceProxy's methods getString(),
// getIStream(), getStringDecideOnPrefix() and getIStreamDecideOnPrefix()
// must:
//
// - use UTF-8 encoding;
//
// - start with:
// * either '/' or ':/' for the 'DecideOnPrefix' variants;
// * only '/' for the other methods.
//
// - have its components separated by slashes;
//
// - not contain any '.' or '..' component.
//
// For the 'DecideOnPrefix' variants:
//
// - if the path starts with a slash ('/'), a real file access is done;
//
// - if, on the other hand, it starts with ':/', EmbeddedResourceProxy uses
// the embedded resource whose virtual path is the specified path without
// its leading ':' (more precisely: the default-locale version of this
// resource).
namespace simgear
{
class EmbeddedResourceProxy
{
public:
// 'virtualRoot' must start with a '/', e.g: '/FGData'. Whether it ends
// with a '/' doesn't make a difference.
explicit EmbeddedResourceProxy(const SGPath& realRoot,
const std::string& virtualRoot,
bool useEmbeddedResourcesByDefault = true);
// Getters and setters for the corresponding data members
SGPath getRealRoot() const;
void setRealRoot(const SGPath& realRoot);
std::string getVirtualRoot() const;
void setVirtualRoot(const std::string& virtualRoot);
bool getUseEmbeddedResources() const;
void setUseEmbeddedResources(bool useEmbeddedResources);
// Get an std::istream to read from a file or from an embedded resource.
std::unique_ptr<std::istream>
getIStream(const std::string& path, bool fromEmbeddedResource) const;
std::unique_ptr<std::istream>
getIStream(const std::string& path) const;
std::unique_ptr<std::istream>
getIStreamDecideOnPrefix(const std::string& path) const;
// Get a file or embedded resource contents as a string.
std::string
getString(const std::string& path, bool fromEmbeddedResource) const;
std::string
getString(const std::string& path) const;
std::string
getStringDecideOnPrefix(const std::string& path) const;
private:
// Check that 'path' starts with either ':/' or '/', and doesn't contain any
// '..' component ('path' may only start with ':/' if 'allowStartWithColon'
// is true).
static void
checkPath(const std::string& callerMethod, const std::string& path,
bool allowStartWithColon);
// Normalize the 'virtualRoot' argument of the constructor. The argument
// must start with a '/' and mustn't contain any '.' or '..' component. The
// return value never ends with a '/'.
static std::string
normalizeVirtualRoot(const std::string& path);
SGPath _realRoot;
std::string _virtualRoot;
bool _useEmbeddedResourcesByDefault;
};
} // of namespace simgear
#endif // of FG_EMBEDDEDRESOURCEPROXY_HXX

View File

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

View File

@@ -21,4 +21,9 @@ else()
endif()
add_test(metar ${EXECUTABLE_OUTPUT_PATH}/test_metar)
add_executable(test_precipitation test_precipitation.cxx)
target_link_libraries(test_precipitation ${TEST_LIBS})
add_test(precipitation ${EXECUTABLE_OUTPUT_PATH}/test_precipitation)
endif(ENABLE_TESTS)

View File

@@ -7,7 +7,7 @@
*
* @brief Precipitation effects to draw rain and snow.
*
* @par Licences
* @par License
* 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
@@ -25,7 +25,6 @@
*/
#include "precipitation.hxx"
//#include "visual_enviro.hxx"
#include <simgear/constants.h>
#include <osg/ClipNode>
@@ -65,7 +64,7 @@ bool SGPrecipitation::getEnabled() const
*/
osg::Group* SGPrecipitation::build(void)
{
osg::Group* group = new osg::Group;
osg::ref_ptr<osg::Group> group = new osg::Group;
_precipitationEffect->snow(0);
_precipitationEffect->rain(0);
@@ -73,8 +72,9 @@ osg::Group* SGPrecipitation::build(void)
if (_clip_distance!=0.0)
{
osg::ref_ptr<osg::ClipNode> clipNode = new osg::ClipNode;
clipNode->addClipPlane( new osg::ClipPlane( 0 ) );
clipNode->getClipPlane(0)->setClipPlane( 0.0, 0.0, -1.0, -_clip_distance );
osg::ref_ptr<osg::ClipPlane> clipPlane = new osg::ClipPlane(0);
clipNode->addClipPlane(clipPlane.get());
clipNode->getClipPlane(0)->setClipPlane(0.0, 0.0, -1.0, -_clip_distance);
clipNode->setReferenceFrame(osg::ClipNode::ABSOLUTE_RF);
clipNode->addChild(_precipitationEffect.get());
@@ -87,7 +87,7 @@ osg::Group* SGPrecipitation::build(void)
group->setNodeMask( ~(simgear::CASTSHADOW_BIT | simgear::MODELLIGHT_BIT) );
return group;
return group.release();
}
@@ -140,8 +140,8 @@ void SGPrecipitation::setRainDropletSize(float size)
/**
* @brief Define the illumination multiplier
*
* This function permits you to define and change the rain droplet size
* which is used if external droplet size control is enabled
* This function permits you to define and change the brightness
* of the precipitation.
*/
void SGPrecipitation::setIllumination(float illumination)
@@ -163,10 +163,9 @@ void SGPrecipitation::setSnowFlakeSize(float size)
/**
* @brief Define the rain droplet size
* @brief Define the clip plane distance
*
* This function permits you to define and change the rain droplet size
* which is used if external droplet size control is enabled
* This function permits you to define and change the clip plane distance.
*/
void SGPrecipitation::setClipDistance(float distance)
@@ -224,7 +223,7 @@ void SGPrecipitation::setWindProperty(double heading, double speed)
* Be careful, if snow and rain intensity are greater than '0', snow effect
* will be first.
*
* The settings come from the osgParticule/PrecipitationEffect.cpp exemple.
* The settings come from the osgParticle/PrecipitationEffect.cpp example.
*/
bool SGPrecipitation::update(void)
{

View File

@@ -34,7 +34,7 @@
class SGPrecipitation : public osg::Referenced
{
private:
protected:
bool _freeze;
bool _enabled;
bool _droplet_external;
@@ -66,7 +66,7 @@ public:
void setIllumination(float);
void setClipDistance(float);
void setEnabled( bool );
void setEnabled(bool);
bool getEnabled() const;
};

View File

@@ -0,0 +1,239 @@
/**************************************************************************
* test_precipitation.cxx -- unit-tests for SGPrecipitation class
*
* Copyright (C) 2017 Scott Giese (xDraconian) - <scttgs0@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
**************************************************************************/
#include <cmath>
#include <memory>
#include <osg/Group>
#include <simgear/constants.h>
#include <simgear/misc/test_macros.hxx>
#include "precipitation.hxx"
using std::cout;
using std::cerr;
using std::endl;
class SGPrecipitationTestFixture : public SGPrecipitation
{
public:
osg::ref_ptr<osg::Group> _group;
void test_configuration()
{
_group = build();
SG_VERIFY(getEnabled());
SG_VERIFY(_precipitationEffect);
SG_CHECK_EQUAL_EP(_rain_intensity, 0.0f);
SG_CHECK_EQUAL_EP(_snow_intensity, 0.0f);
SG_VERIFY(!_freeze);
}
void test_rain()
{
int count = 500;
do {
if (--count == 0) {
cerr << "error: method setRainIntensity took too long.";
exit(EXIT_FAILURE);
}
float saveRainIntensity = _rain_intensity;
setRainIntensity(0.4f);
update();
SG_CHECK_GE(_rain_intensity, saveRainIntensity);
} while (std::fabs(_rain_intensity - 0.4f) > 0.00001f);
SG_CHECK_EQUAL_EP2(_rain_intensity, 0.4f, 0.00001f);
}
void test_rain_external()
{
int count = 500;
do {
if (--count == 0) {
cerr << "error: method setRainIntensity-external took too long.";
exit(EXIT_FAILURE);
}
float saveRainIntensity = _rain_intensity;
setRainIntensity(0.25f);
update();
SG_CHECK_LE(_rain_intensity, saveRainIntensity);
} while (std::fabs(_rain_intensity - 0.25f) > 0.00001f);
// call once more to ensure we have intensity precisely set
setRainIntensity(0.25f);
update();
SG_CHECK_EQUAL_EP2(_rain_intensity, 0.25f, 0.00001f);
}
void test_freeze()
{
float saveParticleSize = _precipitationEffect->getParticleSize();
float saveParticleSpeed = std::fabs(_precipitationEffect->getParticleSpeed());
// change rain to snow
// expect particle size to increase
// expect particle speed to decrease
setFreezing(true);
update();
SG_CHECK_GT(_precipitationEffect->getParticleSize(), saveParticleSize);
SG_CHECK_LT(std::fabs(_precipitationEffect->getParticleSpeed()), saveParticleSpeed);
}
void test_snow()
{
int count = 500;
do {
if (--count == 0) {
cerr << "error: method setSnowIntensity took too long.";
exit(EXIT_FAILURE);
}
float saveSnowIntensity = _snow_intensity;
// not a typo - when freezing is enabled, snow intensity is keep in sync with rain intensity
setRainIntensity(0.3f);
update();
SG_CHECK_LE(_snow_intensity, saveSnowIntensity);
} while (std::fabs(_snow_intensity - 0.3f) > 0.00001f);
SG_CHECK_EQUAL_EP2(_snow_intensity, 0.3f, 0.00001f);
}
void test_snow_external()
{
setRainDropletSize(0.025f);
setSnowFlakeSize(0.04f);
setDropletExternal(true);
int count = 600;
do {
if (--count == 0) {
cerr << "error: method setSnowIntensity-external took too long.";
exit(EXIT_FAILURE);
}
float saveSnowIntensity = _snow_intensity;
setSnowIntensity(0.55f);
update();
SG_CHECK_GE(_snow_intensity, saveSnowIntensity);
} while (std::fabs(_snow_intensity - 0.55f) > 0.00001f);
// call once more to ensure we have intensity precisely set
setSnowIntensity(0.55f);
update();
SG_CHECK_EQUAL_EP2(_snow_intensity, 0.55f, 0.00001f);
}
void test_unfreeze()
{
float saveParticleSize = _precipitationEffect->getParticleSize();
float saveParticleSpeed = std::fabs(_precipitationEffect->getParticleSpeed());
// change snow to rain
// expect particle size to decrease
// expect particle speed to increase
setFreezing(false);
update();
SG_CHECK_LT(_precipitationEffect->getParticleSize(), saveParticleSize);
SG_CHECK_GT(std::fabs(_precipitationEffect->getParticleSpeed()), saveParticleSpeed);
}
void test_no_precipitation()
{
setEnabled(false);
SG_VERIFY(!getEnabled());
update();
// intensity drops to zero, so we should see this reflected in the particles
SG_CHECK_EQUAL_EP(_precipitationEffect->getParticleSize(), 0.01f);
SG_CHECK_EQUAL_EP(_precipitationEffect->getParticleSpeed(), -2.0f);
setEnabled(true);
}
void test_illumination()
{
setIllumination(0.87f);
SG_CHECK_EQUAL_EP(_illumination, 0.87f);
}
void test_clipping()
{
setClipDistance(6.5);
SG_CHECK_EQUAL_EP(_clip_distance, 6.5f);
}
void test_wind()
{
setWindProperty(87.0f, 0.7f);
auto vec = _wind_vec;
SG_CHECK_EQUAL_EP2(vec[0], 0.011166f, 0.00001f);
SG_CHECK_EQUAL_EP2(vec[1], -0.213068f, 0.00001f);
SG_CHECK_EQUAL_EP2(vec[2], 0.0f, 0.00001f);
}
};
int main(int argc, char* argv[])
{
auto fixture = std::unique_ptr<SGPrecipitationTestFixture>(new SGPrecipitationTestFixture);
fixture->test_configuration();
fixture->test_rain();
fixture->test_freeze();
fixture->test_snow();
fixture->test_unfreeze();
fixture->test_no_precipitation();
fixture->test_snow_external();
fixture->test_rain_external();
fixture->test_illumination();
fixture->test_clipping();
fixture->test_wind();
fixture->test_no_precipitation();
cout << "all tests passed OK" << endl;
return EXIT_SUCCESS;
}

View File

@@ -19,6 +19,8 @@
# include <simgear_config.h>
#endif
#include <algorithm>
#include <simgear/compiler.h>
#include "HLAArrayDataType.hxx"

View File

@@ -19,6 +19,8 @@
# include <simgear_config.h>
#endif
#include <algorithm>
#include <simgear/compiler.h>
#include "HLADataType.hxx"

View File

@@ -21,11 +21,11 @@
#include <simgear/compiler.h>
#include "HLAEnumeratedDataType.hxx"
#include <algorithm>
#include <map>
#include <sstream>
#include <vector>
#include "HLAEnumeratedDataType.hxx"
#include "HLADataTypeVisitor.hxx"

View File

@@ -19,10 +19,11 @@
# include <simgear_config.h>
#endif
#include <algorithm>
#include <simgear/compiler.h>
#include "HLAFixedRecordDataType.hxx"
#include "HLADataTypeVisitor.hxx"
#include "HLAFixedRecordDataElement.hxx"

View File

@@ -19,12 +19,12 @@
# include <simgear_config.h>
#endif
#include <algorithm>
#include <simgear/compiler.h>
#include "HLAInteractionClass.hxx"
#include <simgear/debug/logstream.hxx>
#include "HLAInteractionClass.hxx"
#include "HLADataElement.hxx"
#include "HLAFederate.hxx"

View File

@@ -19,6 +19,8 @@
# include <simgear_config.h>
#endif
#include <algorithm>
#include <simgear/compiler.h>
#include "HLAObjectClass.hxx"

View File

@@ -19,10 +19,11 @@
# include <simgear_config.h>
#endif
#include <algorithm>
#include <simgear/compiler.h>
#include "HLAVariantRecordDataType.hxx"
#include "HLADataTypeVisitor.hxx"
#include "HLAVariantRecordDataElement.hxx"

View File

@@ -22,9 +22,13 @@
//
#include <simgear_config.h>
#include <algorithm>
#include "DNSClient.hxx"
#include <udns.h>
#include <time.h>
#include <ctime>
#include <simgear/debug/logstream.hxx>
namespace simgear {

View File

@@ -169,8 +169,32 @@ void Client::update(int waitTimeout)
return;
}
int remainingActive, messagesInQueue, numFds;
int remainingActive, messagesInQueue;
#if defined(SG_MAC)
// Mac 10.8 libCurl lacks this, let's keep compat for now
fd_set curlReadFDs, curlWriteFDs, curlErrorFDs;
int maxFD;
curl_multi_fdset(d->curlMulti,
&curlReadFDs,
&curlWriteFDs,
&curlErrorFDs,
&maxFD);
struct timeval timeout;
long t;
curl_multi_timeout(d->curlMulti, &t);
if ((t < 0) || (t > waitTimeout)) {
t = waitTimeout;
}
timeout.tv_sec = t / 1000;
timeout.tv_usec = (t % 1000) * 1000;
::select(maxFD, &curlReadFDs, &curlWriteFDs, &curlErrorFDs, &timeout);
#else
int numFds;
curl_multi_wait(d->curlMulti, NULL, 0, waitTimeout, &numFds);
#endif
curl_multi_perform(d->curlMulti, &remainingActive);
CURLMsg* msg;

View File

@@ -142,7 +142,7 @@ public:
void finishedRequest(const RepoRequestPtr& req);
HTTPDirectory* getOrCreateDirectory(const std::string& path);
bool deleteDirectory(const std::string& path);
bool deleteDirectory(const std::string& relPath, const SGPath& absPath);
typedef std::vector<HTTPDirectory*> DirectoryVector;
DirectoryVector directories;
@@ -317,7 +317,8 @@ public:
ChildInfoList::iterator c = findIndexChild(it->file());
if (c == children.end()) {
SG_LOG(SG_TERRASYNC, SG_DEBUG, "is orphan '" << it->file() << "'" );
orphans.push_back(it->file());
orphans.push_back(it->file());
} else if (c->hash != hash) {
SG_LOG(SG_TERRASYNC, SG_DEBUG, "hash mismatch'" << it->file() );
// file exists, but hash mismatch, schedule update
@@ -534,7 +535,7 @@ private:
std::string fpath = _relativePath + "/" + name;
if (p.isDir()) {
ok = _repository->deleteDirectory(fpath);
ok = _repository->deleteDirectory(fpath, p);
} else {
// remove the hash cache entry
_repository->updatedFileContents(p, std::string());
@@ -1044,25 +1045,26 @@ HTTPRepository::failure() const
return d;
}
bool HTTPRepoPrivate::deleteDirectory(const std::string& path)
bool HTTPRepoPrivate::deleteDirectory(const std::string& relPath, const SGPath& absPath)
{
DirectoryWithPath p(path);
DirectoryVector::iterator it = std::find_if(directories.begin(), directories.end(), p);
DirectoryWithPath p(relPath);
auto it = std::find_if(directories.begin(), directories.end(), p);
if (it != directories.end()) {
HTTPDirectory* d = *it;
assert(d->absolutePath() == absPath);
directories.erase(it);
Dir dir(d->absolutePath());
bool result = dir.remove(true);
// update the hash cache too
updatedFileContents(d->absolutePath(), std::string());
delete d;
} else {
// we encounter this code path when deleting an orphaned directory
}
Dir dir(absPath);
bool result = dir.remove(true);
return result;
}
// update the hash cache too
updatedFileContents(absPath, std::string());
return false;
return result;
}
void HTTPRepoPrivate::makeRequest(RepoRequestPtr req)

View File

@@ -4,6 +4,7 @@ set(HEADERS
sgstream.hxx
gzfstream.hxx
gzcontainerfile.hxx
CharArrayStream.hxx
zlibstream.hxx
)
@@ -11,6 +12,7 @@ set(SOURCES
sgstream.cxx
gzfstream.cxx
gzcontainerfile.cxx
CharArrayStream.cxx
zlibstream.cxx
)
@@ -22,6 +24,10 @@ if(ENABLE_TESTS)
target_link_libraries(test_streams ${TEST_LIBS})
add_test(streams ${EXECUTABLE_OUTPUT_PATH}/test_streams)
add_executable(test_CharArrayStream CharArrayStream_test.cxx)
target_link_libraries(test_CharArrayStream ${TEST_LIBS})
add_test(CharArrayStream ${EXECUTABLE_OUTPUT_PATH}/test_CharArrayStream)
add_executable(test_zlibstream zlibstream_test.cxx)
target_link_libraries(test_zlibstream ${TEST_LIBS})
add_test(zlibstream ${EXECUTABLE_OUTPUT_PATH}/test_zlibstream)

View File

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

View File

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

View File

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

View File

@@ -38,20 +38,17 @@
#include <zlib.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iostreams/zlibstream.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/structure/exception.hxx>
#include <simgear/debug/logstream.hxx>
using std::string;
using traits = std::char_traits<char>;
using simgear::enumValue;
// 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);
}
using traits = std::char_traits<char>;
// Private utility function
static string zlibErrorMessage(const z_stream& zstream, int errorCode)

View File

@@ -32,10 +32,12 @@
#include <simgear_config.h>
#include "sg_netChannel.hxx"
#include <algorithm>
#include <memory>
#include <cassert>
#include <cerrno>
#include <cstring>
#include <errno.h>
#include <simgear/debug/logstream.hxx>
@@ -258,18 +260,10 @@ NetChannelPoller::removeChannel(NetChannel* channel)
assert(channel);
assert(channel->poller == this);
channel->poller = NULL;
// portability: MSVC throws assertion failure when empty
if (channels.empty()) {
return;
}
ChannelList::iterator it = channels.begin();
for (; it != channels.end(); ++it) {
if (*it == channel) {
channels.erase(it);
return;
}
auto it = std::find(channels.begin(), channels.end(), channel);
if (it != channels.end()) {
channels.erase(it);
}
}

View File

@@ -183,15 +183,6 @@ public:
int requestContentLength;
};
class EraseIfClosed
{
public:
bool operator()(simgear::NetChannel* chan) const
{
return chan->isClosed();
}
};
template <class T>
class TestServer : public NetChannel
{
@@ -202,7 +193,6 @@ public:
{
Socket::initSockets();
open();
bind(NULL, 2000); // localhost, any port
listen(16);
@@ -212,6 +202,7 @@ public:
virtual ~TestServer()
{
_poller.removeChannel(this);
}
virtual bool writable (void) { return false ; }
@@ -231,15 +222,16 @@ public:
{
_poller.poll();
typename std::vector<T*>::iterator it;
it = std::remove_if(_channels.begin(), _channels.end(), EraseIfClosed());
for (typename std::vector<T*>::iterator it2 = it; it2 != _channels.end(); ++it2) {
delete *it2;
}
auto it = std::remove_if(_channels.begin(), _channels.end(), [&](T* channel) {
if (channel->isClosed()) {
_poller.removeChannel(channel);
delete channel;
return true;
}
return false;
});
_channels.erase(it, _channels.end());
}
int connectCount()

View File

@@ -33,6 +33,7 @@ void testTarGz()
SG_VERIFY(TarExtractor::isTarData(buf, bufSize));
f.close();
}
void testPlainTar()
@@ -48,12 +49,13 @@ void testPlainTar()
SG_VERIFY(TarExtractor::isTarData(buf, bufSize));
f.close();
}
int main (int ac, char ** av)
int main(int ac, char ** av)
{
testTarGz();
testPlainTar();
return 0;
return 0;
}

View File

@@ -28,6 +28,7 @@
#include <zlib.h>
#include <simgear/sg_inlines.h>
#include <simgear/io/sg_file.hxx>
#include <simgear/misc/sg_dir.hxx>
@@ -92,7 +93,8 @@ public:
END_OF_ARCHIVE,
ERROR_STATE, ///< states above this are error conditions
BAD_ARCHIVE,
BAD_DATA
BAD_DATA,
FILTER_STOPPED
} State;
SGPath path;
@@ -110,10 +112,13 @@ public:
bool haveInitedZLib;
bool uncompressedData; // set if reading a plain .tar (not tar.gz)
uint8_t* headerPtr;
TarExtractorPrivate() :
TarExtractor* outer;
bool skipCurrentEntry = false;
TarExtractorPrivate(TarExtractor* o) :
haveInitedZLib(false),
uncompressedData(false)
uncompressedData(false),
outer(o)
{
}
@@ -129,7 +134,10 @@ public:
}
if (state == READING_FILE) {
currentFile->close();
if (currentFile) {
currentFile->close();
currentFile.reset();
}
size_t pad = currentFileSize % TAR_HEADER_BLOCK_SIZE;
if (pad) {
bytesRemaining = TAR_HEADER_BLOCK_SIZE - pad;
@@ -177,26 +185,36 @@ public:
return;
}
skipCurrentEntry = false;
std::string tarPath = std::string(header.prefix) + std::string(header.fileName);
if (!isSafePath(tarPath)) {
//state = BAD_ARCHIVE;
SG_LOG(SG_IO, SG_WARN, "bad tar path:" << tarPath);
//return;
skipCurrentEntry = true;
}
SGPath p = path;
p.append(tarPath);
auto result = outer->filterPath(tarPath);
if (result == TarExtractor::Stop) {
setState(FILTER_STOPPED);
return;
} else if (result == TarExtractor::Skipped) {
skipCurrentEntry = true;
}
SGPath p = path / tarPath;
if (header.typeflag == DIRTYPE) {
Dir dir(p);
dir.create(0755);
if (!skipCurrentEntry) {
Dir dir(p);
dir.create(0755);
}
setState(READING_HEADER);
} else if ((header.typeflag == REGTYPE) || (header.typeflag == AREGTYPE)) {
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
currentFile.reset(new SGBinaryFile(p));
currentFile->open(SG_IO_OUT);
if (!skipCurrentEntry) {
currentFile.reset(new SGBinaryFile(p));
currentFile->open(SG_IO_OUT);
}
setState(READING_FILE);
} else {
SG_LOG(SG_IO, SG_WARN, "Unsupported tar file type:" << header.typeflag);
@@ -212,7 +230,9 @@ public:
size_t curBytes = std::min(bytesRemaining, count);
if (state == READING_FILE) {
currentFile->write(bytes, curBytes);
if (currentFile) {
currentFile->write(bytes, curBytes);
}
bytesRemaining -= curBytes;
} else if ((state == READING_HEADER) || (state == PRE_END_OF_ARCHVE) || (state == END_OF_ARCHIVE)) {
memcpy(headerPtr, bytes, curBytes);
@@ -264,7 +284,7 @@ public:
};
TarExtractor::TarExtractor(const SGPath& rootPath) :
d(new TarExtractorPrivate)
d(new TarExtractorPrivate(this))
{
d->path = rootPath;
@@ -383,22 +403,26 @@ bool TarExtractor::isTarData(const uint8_t* bytes, size_t count)
z.avail_in = count;
if (inflateInit2(&z, ZLIB_INFLATE_WINDOW_BITS | ZLIB_DECODE_GZIP_HEADER) != Z_OK) {
inflateEnd(&z);
return false;
}
int result = inflate(&z, Z_SYNC_FLUSH);
if (result != Z_OK) {
SG_LOG(SG_IO, SG_WARN, "inflate failed:" << result);
inflateEnd(&z);
return false; // not tar data
}
size_t written = 4096 - z.avail_out;
if (written < TAR_HEADER_BLOCK_SIZE) {
SG_LOG(SG_IO, SG_WARN, "insufficient data for header");
inflateEnd(&z);
return false;
}
header = reinterpret_cast<UstarHeaderBlock*>(zlibOutput);
inflateEnd(&z);
} else {
// uncompressed tar
if (count < TAR_HEADER_BLOCK_SIZE) {
@@ -417,4 +441,11 @@ bool TarExtractor::isTarData(const uint8_t* bytes, size_t count)
return true;
}
auto TarExtractor::filterPath(std::string& pathToExtract)
-> PathResult
{
SG_UNUSED(pathToExtract);
return Accepted;
}
} // of simgear

View File

@@ -43,7 +43,17 @@ public:
bool hasError() const;
protected:
enum PathResult {
Accepted,
Skipped,
Modified,
Stop
};
virtual PathResult filterPath(std::string& pathToExtract);
private:
friend class TarExtractorPrivate;
std::unique_ptr<TarExtractorPrivate> d;
};

View File

@@ -155,6 +155,12 @@ public:
{
return std::isnan(v);
}
static bool eq(const T& a, const T& b, const T& epsilon = SGLimits<T>::epsilon())
{ return std::abs(a - b) < epsilon; }
static bool neq(const T& a, const T& b, const T& epsilon = SGLimits<T>::epsilon())
{ return !eq(a, b, epsilon); }
};
#endif

View File

@@ -2,7 +2,11 @@
#include <simgear/compiler.h>
#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <cstring>
@@ -286,6 +290,49 @@ void test_permissions()
SG_CHECK_EQUAL(fileInRW.canWrite(), false);
}
void test_comparisons()
{
std::cout << "Testing comparisons\n";
SG_CHECK_EQUAL(SGPath("/abc/def ghi"), SGPath("/abc/def ghi"));
SG_CHECK_NE(SGPath("/abc"), SGPath("abc"));
SG_CHECK_LT(SGPath(""), SGPath("/"));
SG_CHECK_LT(SGPath("A"), SGPath("a"));
SG_CHECK_LE(SGPath(""), SGPath("/"));
SG_CHECK_LE(SGPath("/"), SGPath("/"));
SG_CHECK_GT(SGPath("a"), SGPath("A"));
SG_CHECK_GE(SGPath("a"), SGPath("A"));
SG_CHECK_GE(SGPath("a"), SGPath("a"));
std::vector<SGPath> origVector({
std::string("/zer/gh/tr aze"),
std::string("/abc/def/ttt"),
std::string("/abc/def/ddd"),
std::string("/a"),
std::string("")});
std::vector<SGPath> sortedVector({
std::string(""),
std::string("/a"),
std::string("/abc/def/ddd"),
std::string("/abc/def/ttt"),
std::string("/zer/gh/tr aze")});
std::sort(origVector.begin(), origVector.end());
SG_CHECK_EQUAL_NOSTREAM(origVector, sortedVector);
}
void test_hash_function()
{
std::cout << "Testing the std::hash<SGPath> specialization\n";
const SGPath nullPath{};
const SGPath p{"/abc/def"};
SG_CHECK_EQUAL(std::hash<SGPath>{}(nullPath), std::hash<SGPath>{}(nullPath));
SG_CHECK_EQUAL(std::hash<SGPath>{}(p), std::hash<SGPath>{}(p));
SG_CHECK_NE(std::hash<SGPath>{}(p), std::hash<SGPath>{}(p / "foobar"));
}
int main(int argc, char* argv[])
{
SGPath pa;
@@ -389,12 +436,11 @@ int main(int argc, char* argv[])
SG_CHECK_EQUAL(pp.canWrite(), false);
test_dir();
test_path_dir();
test_path_dir();
test_permissions();
test_update_dir();
test_update_dir();
test_comparisons();
test_hash_function();
cout << "all tests passed OK" << endl;
return 0; // passed

View File

@@ -296,7 +296,10 @@ bool Dir::isEmpty() const
int n = 0;
dirent* d;
while( (d = readdir(dp)) !=NULL && (n < 4) ) n++;
while (n < 3 && (d = readdir(dp)) != nullptr) {
n++;
}
closedir(dp);
return (n == 2); // '.' and '..' always exist

View File

@@ -2,6 +2,7 @@
#include <cstdlib>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/test_macros.hxx>
#include "sg_dir.hxx"
@@ -34,11 +35,35 @@ void test_tempDir()
d.remove();
}
void test_isEmpty()
{
simgear::Dir d = simgear::Dir::tempDir("FlightGear");
SG_VERIFY(!d.isNull() && d.exists() && d.isEmpty());
SGPath f = d.file("some file");
{ sg_ofstream file(f); } // create and close the file
SG_VERIFY(!d.isEmpty());
f.remove();
SG_VERIFY(d.isEmpty());
simgear::Dir subDir{d.file("some subdir")};
subDir.create(0777);
SG_VERIFY(!d.isEmpty());
subDir.remove();
SG_VERIFY(d.isEmpty());
d.remove();
SG_VERIFY(d.isEmpty()); // eek, but that's how it is
}
int main(int argc, char **argv)
{
test_isNull();
test_setRemoveOnDestroy();
test_tempDir();
test_isEmpty();
return EXIT_SUCCESS;
}

View File

@@ -35,9 +35,16 @@
#include <fstream>
#include <cstdlib>
#ifdef _WIN32
# include <direct.h>
#if !defined(SG_WINDOWS)
# include <sys/types.h>
# include <utime.h>
#endif
#if defined(SG_WINDOWS)
# include <direct.h>
# include <sys/utime.h>
#endif
#include "sg_path.hxx"
#include <boost/algorithm/string/case_conv.hpp>
@@ -52,13 +59,13 @@ using simgear::strutils::starts_with;
static const char sgDirPathSep = '/';
static const char sgDirPathSepBad = '\\';
#ifdef _WIN32
#if defined(SG_WINDOWS)
const char SGPath::pathListSep[] = ";"; // this is null-terminated
#else
const char SGPath::pathListSep[] = ":"; // ditto
#endif
#ifdef _WIN32
#if defined(SG_WINDOWS)
#include <ShlObj.h> // for CSIDL
// TODO: replace this include file with the official <versionhelpers.h> header
// included in the Windows 8.1 SDK
@@ -240,44 +247,6 @@ SGPath SGPath::fromUtf8(const std::string& bytes, PermissionChecker p)
return SGPath(bytes, p);
}
SGPath::SGPath(const SGPath& p) :
path(p.path),
_permission_checker(p._permission_checker),
_cached(p._cached),
_rwCached(p._rwCached),
_cacheEnabled(p._cacheEnabled),
_canRead(p._canRead),
_canWrite(p._canWrite),
_exists(p._exists),
_isDir(p._isDir),
_isFile(p._isFile),
_modTime(p._modTime),
_size(p._size)
{
}
SGPath& SGPath::operator=(const SGPath& p)
{
path = p.path;
_permission_checker = p._permission_checker,
_cached = p._cached;
_rwCached = p._rwCached;
_cacheEnabled = p._cacheEnabled;
_canRead = p._canRead;
_canWrite = p._canWrite;
_exists = p._exists;
_isDir = p._isDir;
_isFile = p._isFile;
_modTime = p._modTime;
_size = p._size;
return *this;
}
// destructor
SGPath::~SGPath() {
}
// set path
void SGPath::set( const string& p ) {
path = p;
@@ -780,6 +749,18 @@ bool SGPath::operator!=(const SGPath& other) const
return (path != other.path);
}
bool operator<(const SGPath& lhs, const SGPath& rhs)
{ return lhs.path < rhs.path; }
bool operator>(const SGPath& lhs, const SGPath& rhs)
{ return operator<(rhs, lhs); }
bool operator<=(const SGPath& lhs, const SGPath& rhs)
{ return !operator>(lhs, rhs); }
bool operator>=(const SGPath& lhs, const SGPath& rhs)
{ return !operator<(lhs, rhs); }
//------------------------------------------------------------------------------
bool SGPath::rename(const SGPath& newName)
{
@@ -1054,3 +1035,60 @@ bool SGPath::permissionsAllowsWrite() const
return _permission_checker ? _permission_checker(*this).write : true;
}
//------------------------------------------------------------------------------
std::string SGPath::fileUrl() const
{
// we should really URL encode the names here?
if (isAbsolute()) {
// check for a windows drive letter
#if defined(SG_WINDOWS)
if (isalpha(path.front())) {
// file URLs on Windows must look like file:///C:/Foo/Bar
return "file:///" + utf8Str();
}
#endif
// the leading directory seperator of the path becomes the required
// third slash in this case.
return "file://" + utf8Str();
} else {
SG_LOG(SG_GENERAL, SG_WARN, "Cannot convert relative path to a URL:" << path);
return {};
}
}
//------------------------------------------------------------------------------
bool SGPath::touch()
{
if (!permissionsAllowsWrite())
{
SG_LOG(SG_IO, SG_WARN, "file touch failed: (" << *this << ")"
" reason: access denied" );
return false;
}
if (!exists()) {
SG_LOG(SG_IO, SG_WARN, "file touch failed: (" << *this << ")"
" reason: missing file");
return false;
}
#if defined(SG_WINDOWS)
auto ws = wstr();
// set this link for docs on behaviour here, about passing nullptr
// https://msdn.microsoft.com/en-us/library/aa273399(v=vs.60).aspx
if (_wutime(ws.c_str(), nullptr) != 0) {
SG_LOG(SG_IO, SG_WARN, "file touch failed: (" << *this << ")"
" reason: _wutime failed with error:" << simgear::strutils::error_string(errno));
return false;
}
#else
if (::utime(path.c_str(), nullptr) != 0) {
SG_LOG(SG_IO, SG_WARN, "file touch failed: (" << *this << ")"
" reason: utime failed with error:" << simgear::strutils::error_string(errno));
return false;
}
#endif
// reset the cache flag so we re-stat() on next request
_cached = false;
return true;
}

View File

@@ -28,12 +28,14 @@
#ifndef _SG_PATH_HXX
#define _SG_PATH_HXX
#include <functional>
#include <string>
#include <cstdlib>
#include <ctime>
#include <sys/types.h>
#include <simgear/compiler.h>
#include <string>
#include <ctime>
#include <simgear/math/sg_types.hxx>
#ifdef _MSC_VER
@@ -65,11 +67,6 @@ public:
/** Default constructor */
explicit SGPath(PermissionChecker validator = NULL);
/** Copy contructor */
SGPath(const SGPath& p);
SGPath& operator=(const SGPath& p);
/**
* Construct a path based on the starting path provided.
* @param p initial path
@@ -87,9 +84,6 @@ public:
const std::string& r,
PermissionChecker validator = NULL );
/** Destructor */
~SGPath();
/**
* Set path to a new value
* @param p new path
@@ -99,6 +93,8 @@ public:
bool operator==(const SGPath& other) const;
bool operator!=(const SGPath& other) const;
// Other comparison operators are declared below
friend bool operator<(const SGPath& lhs, const SGPath& rhs);
void setPermissionChecker(PermissionChecker validator);
PermissionChecker getPermissionChecker() const;
@@ -190,8 +186,8 @@ public:
* Get the path string
* @return path string
*/
std::string str() const { return path; }
std::string utf8Str() const { return path; }
std::string str() const noexcept { return path; }
std::string utf8Str() const noexcept { return path; }
std::string local8BitStr() const;
@@ -281,6 +277,18 @@ public:
*/
SGPath dirPath() const;
/*
* return path as a file:// URI
*/
std::string fileUrl() const;
/**
* Update the file modification timestamp to be 'now'. The contents will
* not be changed. (Same as POSIX 'touch' command). Will fail if the file
* does not exist or permissions do not allow writing.
*/
bool touch();
enum StandardLocation
{
HOME,
@@ -352,6 +360,24 @@ private:
mutable size_t _size;
};
// Other comparison operators are in the class definition block
bool operator> (const SGPath& lhs, const SGPath& rhs);
bool operator<=(const SGPath& lhs, const SGPath& rhs);
bool operator>=(const SGPath& lhs, const SGPath& rhs);
// Hash function for SGPath
namespace std
{
template<>
struct hash<SGPath>
{
std::size_t operator()(const SGPath& path) const noexcept
{
return std::hash<std::string>{}(path.utf8Str());
}
};
} // of namespace std
/// Output to an ostream
template<typename char_type, typename traits_type>
inline

View File

@@ -29,10 +29,7 @@
#include <cstring> // strerror_r() and strerror_s()
#include <cctype>
#include <cerrno>
#if defined(HAVE_CPP11_CODECVT)
#include <codecvt> // new in C++11
#endif
#include <cassert>
#include "strutils.hxx"
@@ -44,6 +41,8 @@
#if defined(SG_WINDOWS)
#include <windows.h>
#include <codecvt>
#include <locale>
#endif
using std::string;
@@ -538,6 +537,33 @@ namespace simgear {
unsigned long long readNonNegativeInt<unsigned long long, 16>(
const std::string& s);
#endif
// parse a time string ([+/-]%f[:%f[:%f]]) into hours
double readTime(const string& time_in)
{
if (time_in.empty()) {
return 0.0;
}
const bool negativeSign = time_in.front() == '-';
const string_list pieces = split(time_in, ":");
if (pieces.size() > 3) {
throw sg_format_exception("Unable to parse time string, too many pieces", time_in);
}
const int hours = std::abs(to_int(pieces.front()));
int minutes = 0, seconds = 0;
if (pieces.size() > 1) {
minutes = to_int(pieces.at(1));
if (pieces.size() > 2) {
seconds = to_int(pieces.at(2));
}
}
double result = hours + (minutes / 60.0) + (seconds / 3600.0);
return negativeSign ? -result : result;
}
int compare_versions(const string& v1, const string& v2, int maxComponents)
{
@@ -629,25 +655,85 @@ static std::string convertWStringToMultiByte(DWORD encoding, const std::wstring&
std::wstring convertUtf8ToWString(const std::string& a)
{
#ifdef SG_WINDOWS
return convertMultiByteToWString(CP_UTF8, a);
#elif defined(HAVE_CPP11_CODECVT)
#if defined(SG_WINDOWS)
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> ucs2conv;
return ucs2conv.from_bytes(a);
#else
return std::wstring();
assert(sizeof(wchar_t) == 4);
std::wstring result;
int expectedContinuationCount = 0;
wchar_t wc = 0;
for (uint8_t utf8CodePoint : a) {
// ASCII 7-bit range
if (utf8CodePoint <= 0x7f) {
if (expectedContinuationCount != 0) {
throw sg_format_exception();
}
result.push_back(static_cast<wchar_t>(utf8CodePoint));
} else if (expectedContinuationCount > 0) {
if ((utf8CodePoint & 0xC0) != 0x80) {
throw sg_format_exception();
}
wc = (wc << 6) | (utf8CodePoint & 0x3F);
if (--expectedContinuationCount == 0) {
result.push_back(wc);
}
} else {
if ((utf8CodePoint & 0xE0) == 0xC0) {
expectedContinuationCount = 1;
wc = utf8CodePoint & 0x1f;
} else if ((utf8CodePoint & 0xF0) == 0xE0) {
expectedContinuationCount = 2;
wc = utf8CodePoint & 0x0f;
} else if ((utf8CodePoint & 0xF8) == 0xF0) {
expectedContinuationCount = 3;
wc =utf8CodePoint & 0x07;
} else {
// illegal UTF-8 encoding
throw sg_format_exception();
}
}
} // of UTF-8 code point iteration
return result;
#endif
}
std::string convertWStringToUtf8(const std::wstring& w)
{
#ifdef SG_WINDOWS
return convertWStringToMultiByte(CP_UTF8, w);
#elif defined(HAVE_CPP11_CODECVT)
#if defined(SG_WINDOWS)
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> ucs2conv;
return ucs2conv.to_bytes(w);
#else
return std::string();
assert(sizeof(wchar_t) == 4);
std::string result;
for (wchar_t cp : w) {
if (cp <= 0x7f) {
result.push_back(static_cast<uint8_t>(cp));
} else if (cp <= 0x07ff) {
result.push_back(0xC0 | ((cp >> 6) & 0x1f));
result.push_back(0x80 | (cp & 0x3f));
} else if (cp <= 0xffff) {
result.push_back(0xE0 | ((cp >> 12) & 0x0f));
result.push_back(0x80 | ((cp >> 6) & 0x3f));
result.push_back(0x80 | (cp & 0x3f));
} else if (cp < 0x10ffff) {
result.push_back(0xF0 | ((cp >> 18) & 0x07));
result.push_back(0x80 | ((cp >> 12) & 0x3f));
result.push_back(0x80 | ((cp >> 6) & 0x3f));
result.push_back(0x80 | (cp & 0x3f));
} else {
throw sg_format_exception();
}
}
return result;
#endif
}

View File

@@ -209,6 +209,16 @@ namespace simgear {
typename = typename std::enable_if<std::is_integral<T>::value, T>::type >
T readNonNegativeInt(const std::string& s);
/**
* Read a time value, seperated by colons, as a value in hours.
* Allowable input is ([+/-]%f[:%f[:%f]])
* i.e 15:04:35 is parsed as 15 + (04 / 60) + (35 / 2600)
* This code is moved from flightgear's options.cxx where it was called
* parse_time(),
*/
double readTime(const std::string& s);
/**
* Convert a string representing a boolean, to a bool.
* Accepted values include YES, true, 0, 1, false, no, True,

View File

@@ -19,6 +19,7 @@
#include <simgear/compiler.h>
#include <simgear/misc/strutils.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/constants.h>
using std::string;
using std::vector;
@@ -581,6 +582,43 @@ void test_error_string()
SG_CHECK_GT(strutils::error_string(saved_errno).size(), 0);
}
void test_readTime()
{
SG_CHECK_EQUAL_EP(strutils::readTime(""), 0.0);
SG_CHECK_EQUAL_EP(strutils::readTime("11"), 11.0);
SG_CHECK_EQUAL_EP(strutils::readTime("+11"), 11.0);
SG_CHECK_EQUAL_EP(strutils::readTime("-11"), -11.0);
SG_CHECK_EQUAL_EP(strutils::readTime("11:30"), 11.5);
SG_CHECK_EQUAL_EP(strutils::readTime("+11:15"), 11.25);
SG_CHECK_EQUAL_EP(strutils::readTime("-11:45"), -11.75);
const double seconds = 1 / 3600.0;
SG_CHECK_EQUAL_EP(strutils::readTime("11:30:00"), 11.5);
SG_CHECK_EQUAL_EP(strutils::readTime("+11:15:05"), 11.25 + 5 * seconds);
SG_CHECK_EQUAL_EP(strutils::readTime("-11:45:15"), -(11.75 + 15 * seconds));
SG_CHECK_EQUAL_EP(strutils::readTime("0:0:0"), 0);
SG_CHECK_EQUAL_EP(strutils::readTime("0:0:28"), 28 * seconds);
SG_CHECK_EQUAL_EP(strutils::readTime("-0:0:28"), -28 * seconds);
}
void test_utf8Convert()
{
// F, smiley emoticon, Maths summation symbol, section sign
std::wstring a(L"\u0046\U0001F600\u2211\u00A7");
std::string utf8A = strutils::convertWStringToUtf8(a);
SG_VERIFY(utf8A == std::string("F\xF0\x9F\x98\x80\xE2\x88\x91\xC2\xA7"));
std::wstring aRoundTrip = strutils::convertUtf8ToWString(utf8A);
SG_VERIFY(a == aRoundTrip);
}
int main(int argc, char* argv[])
{
test_strip();
@@ -599,6 +637,8 @@ int main(int argc, char* argv[])
test_md5_hex();
test_error_string();
test_propPathMatch();
test_readTime();
test_utf8Convert();
return EXIT_SUCCESS;
}

View File

@@ -787,6 +787,11 @@ void naGCRelease(int key)
naHash_delete(globals->save_hash, naNum(key));
}
int naNumSaved()
{
return naHash_size(globals->save_hash) + naVec_size(globals->save);
}
void naClearSaved()
{
naContext c;

View File

@@ -1,10 +1,13 @@
include (SimGearComponent)
set(HEADERS
cppbind_fwd.hxx
Ghost.hxx
NasalCallContext.hxx
NasalContext.hxx
NasalHash.hxx
NasalMe.hxx
NasalMethodHolder.hxx
NasalObject.hxx
NasalObjectHolder.hxx
NasalString.hxx
@@ -13,11 +16,8 @@ set(HEADERS
)
set(DETAIL_HEADERS
detail/from_nasal_function_templates.hxx
detail/from_nasal_helper.hxx
detail/functor_templates.hxx
detail/nasal_traits.hxx
detail/NasalObject_callMethod_templates.hxx
detail/to_nasal_helper.hxx
)
@@ -52,4 +52,4 @@ add_boost_test(nasal_gc_test
add_boost_test(nasal_num
SOURCES test/nasal_num_test.cxx
LIBRARIES ${TEST_LIBS}
)
)

View File

@@ -24,17 +24,16 @@
#include "NasalObjectHolder.hxx"
#include <simgear/debug/logstream.hxx>
#include <simgear/std/integer_sequence.hxx>
#include <simgear/std/type_traits.hxx>
#include <simgear/structure/SGWeakReferenced.hxx>
#include <simgear/structure/SGWeakPtr.hxx>
#include <boost/bind.hpp>
#include <boost/call_traits.hpp>
#include <boost/function.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/mpl/has_xxx.hpp>
#include <boost/preprocessor/iteration/iterate.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/utility/enable_if.hpp>
#include <map>
@@ -62,10 +61,7 @@ namespace osg
}
template<class T>
inline typename boost::enable_if<
boost::is_pointer<T>,
T
>::type
inline std::enable_if_t<std::is_pointer<T>::value, T>
get_pointer(T ptr)
{
return ptr;
@@ -101,8 +97,8 @@ namespace nasal
class GhostMetadata
{
public:
typedef void(*Deleter)(void*);
typedef std::vector<std::pair<Deleter, void*> > DestroyList;
using Deleter = void(*)(void*);
using DestroyList = std::vector<std::pair<Deleter, void*>>;
static DestroyList _destroy_list;
@@ -154,19 +150,6 @@ namespace nasal
};
BOOST_MPL_HAS_XXX_TRAIT_DEF(element_type)
template<class T>
struct reduced_type
{
typedef typename boost::remove_cv<
typename boost::remove_reference<T>::type
>::type type;
};
template<class T1, class T2>
struct reduced_is_same:
public boost::is_same<typename reduced_type<T1>::type, T2>
{};
}
/** @brief Destroy all ghost queued for deletion.
@@ -178,8 +161,8 @@ namespace nasal
*/
void ghostProcessDestroyList();
typedef SGSharedPtr<internal::MethodHolder> MethodHolderPtr;
typedef SGWeakPtr<internal::MethodHolder> MethodHolderWeakPtr;
using MethodHolderPtr = SGSharedPtr<internal::MethodHolder>;
using MethodHolderWeakPtr = SGWeakPtr<internal::MethodHolder>;
// Dummy template to create shorter and easy to understand compile errors if
// trying to wrap the wrong type as a Ghost.
@@ -187,9 +170,10 @@ namespace nasal
class Ghost
{
public:
BOOST_STATIC_ASSERT(("Ghost can only wrap shared pointer!"
&& is_strong_ref<T>::value
));
static_assert(
is_strong_ref<T>::value,
"Ghost can only wrap shared pointer!"
);
static Ghost& init(const std::string& name);
static bool isInit();
@@ -209,7 +193,7 @@ namespace nasal
* int myMember();
* void doSomethingElse(const nasal::CallContext& ctx);
* }
* typedef boost::shared_ptr<MyClass> MyClassPtr;
* using MyClassPtr = boost::shared_ptr<MyClass>;
*
* std::string myOtherFreeMember(int num);
*
@@ -235,29 +219,25 @@ namespace nasal
* @endcode
*/
template<class T>
class Ghost<T, typename boost::enable_if<is_strong_ref<T> >::type>:
class Ghost<T, std::enable_if_t<is_strong_ref<T>::value>>:
public internal::GhostMetadata
{
// Shared pointer required for Ghost (no weak pointer!)
BOOST_STATIC_ASSERT((is_strong_ref<T>::value));
public:
typedef typename T::element_type raw_type;
typedef typename shared_ptr_traits<T>::strong_ref strong_ref;
typedef typename shared_ptr_traits<T>::weak_ref weak_ref;
typedef naRef (raw_type::*member_func_t)(const CallContext&);
typedef naRef (*free_func_t)(raw_type&, const CallContext&);
typedef boost::function<naRef(raw_type&, naContext)> getter_t;
typedef boost::function<void( raw_type&, naContext, naRef)> setter_t;
typedef boost::function<naRef(raw_type&, const CallContext&)> method_t;
typedef boost::function<bool( raw_type&,
naContext,
const std::string&,
naRef& )> fallback_getter_t;
typedef boost::function<bool( raw_type&,
naContext,
const std::string&,
naRef )> fallback_setter_t;
using raw_type = typename T::element_type;
using strong_ref = typename shared_ptr_traits<T>::strong_ref;
using weak_ref = typename shared_ptr_traits<T>::weak_ref;
using member_func_t = naRef (raw_type::*)(const CallContext&);
using free_func_t = naRef (*)(raw_type&, const CallContext&);
using getter_t = boost::function<naRef(raw_type&, naContext)>;
using setter_t = boost::function<void( raw_type&, naContext, naRef)>;
using method_t = boost::function<naRef(raw_type&, const CallContext&)>;
using fallback_getter_t =
boost::function<bool(raw_type&, naContext, const std::string&, naRef&)>;
using fallback_setter_t =
boost::function<bool(raw_type&, naContext, const std::string&, naRef)>;
template<class Ret, class... Args>
using method_variadic_t = boost::function<Ret (raw_type&, Args...)>;
class MethodHolder:
public internal::MethodHolder
@@ -269,8 +249,8 @@ namespace nasal
protected:
typedef SGSharedPtr<MethodHolder> SharedPtr;
typedef SGWeakPtr<MethodHolder> WeakPtr;
using SharedPtr = SGSharedPtr<MethodHolder>;
using WeakPtr = SGWeakPtr<MethodHolder>;
method_t _method;
@@ -369,7 +349,7 @@ namespace nasal
MethodHolderPtr func;
};
typedef std::map<std::string, member_t> MemberMap;
using MemberMap = std::map<std::string, member_t>;
/**
* Register a new ghost type.
@@ -406,19 +386,18 @@ namespace nasal
* @endcode
*/
template<class BaseGhost>
typename boost::enable_if
<
boost::is_base_of<GhostMetadata, BaseGhost>,
Ghost
>::type&
std::enable_if_t<
std::is_base_of<GhostMetadata, BaseGhost>::value,
Ghost&
>
bases()
{
BOOST_STATIC_ASSERT
((
boost::is_base_of<typename BaseGhost::raw_type, raw_type>::value
));
static_assert(
std::is_base_of<typename BaseGhost::raw_type, raw_type>::value,
"Not a base class!"
);
typedef typename BaseGhost::strong_ref base_ref;
using base_ref = typename BaseGhost::strong_ref;
BaseGhost* base = BaseGhost::getSingletonPtr();
base->addDerived(
@@ -429,17 +408,14 @@ namespace nasal
// Replace any getter that is not available in the current class.
// TODO check if this is the correct behavior of function overriding
for( typename BaseGhost::MemberMap::const_iterator member =
base->_members.begin();
member != base->_members.end();
++member )
for(auto const& base_member: base->_members)
{
if( _members.find(member->first) == _members.end() )
_members[member->first] = member_t
if( _members.find(base_member.first) == _members.end() )
_members[base_member.first] = member_t
(
member->second.getter,
member->second.setter,
member->second.func
base_member.second.getter,
base_member.second.setter,
base_member.second.func
);
}
@@ -464,17 +440,16 @@ namespace nasal
* @endcode
*/
template<class Base>
typename boost::disable_if
<
boost::is_base_of<GhostMetadata, Base>,
Ghost
>::type&
std::enable_if_t<
!std::is_base_of<GhostMetadata, Base>::value,
Ghost&
>
bases()
{
BOOST_STATIC_ASSERT
((
boost::is_base_of<typename Ghost<Base>::raw_type, raw_type>::value
));
static_assert(
std::is_base_of<typename Ghost<Base>::raw_type, raw_type>::value,
"Not a base class!"
);
return bases< Ghost<Base> >();
}
@@ -770,7 +745,7 @@ namespace nasal
/**
* Register anything that accepts an object instance and a
* nasal::CallContext whith automatic conversion of the return type to
* nasal::CallContext with automatic conversion of the return type to
* Nasal.
*
* @code{cpp}
@@ -791,14 +766,73 @@ namespace nasal
return method(name, boost::bind(method_invoker<Ret>, func, _1, _2));
}
// Build dependency for CMake, gcc, etc.
#define SG_DONT_DO_ANYTHING
# include <simgear/nasal/cppbind/detail/functor_templates.hxx>
#undef SG_DONT_DO_ANYTHING
/**
* Bind any callable entity accepting an instance of raw_type and an
* arbitrary number of arguments as method.
*
* The std::index_sequence specifies the order of the arguments
*/
template<class Ret, class... Args, std::size_t... Indices>
Ghost& method( const std::string& name,
const method_variadic_t<Ret, Args...>& func,
std::index_sequence<Indices...> )
{
return method<Ret>
(
name,
typename boost::function<Ret (raw_type&, const CallContext&)>
( boost::bind(
func,
_1,
boost::bind(&Ghost::arg_from_nasal<Args>, _2, Indices)...
))
);
}
#define BOOST_PP_ITERATION_LIMITS (0, 9)
#define BOOST_PP_FILENAME_1 <simgear/nasal/cppbind/detail/functor_templates.hxx>
#include BOOST_PP_ITERATE()
template<class Ret, class... Args>
Ghost& method( const std::string& name,
const method_variadic_t<Ret, Args...>& func )
{
return method(name, func, std::index_sequence_for<Args...>{});
}
/**\
* Bind a member function with an arbitrary number of arguments as method.
*/
template<class Ret, class... Args>
Ghost& method( const std::string& name,
Ret (raw_type::*fn)(Args...) )
{
return method(name, method_variadic_t<Ret, Args...>(fn));
}
template<class Ret, class... Args>
Ghost& method( const std::string& name,
Ret (raw_type::*fn)(Args...) const )
{
return method(name, method_variadic_t<Ret, Args...>(fn));
}
/**
* Bind free function accepting an instance of raw_type and an arbitrary
* number of arguments as method.
*/
template<class Ret, class Type, class... Args>
Ghost& method
(
const std::string& name,
Ret (*fn)(Type, Args ... args)
)
{
static_assert(
std::is_convertible<raw_type&, Type>::value,
//|| std::is_convertible<raw_type*, Type>::value
// TODO check how to do it with pointer...
"First parameter can not be converted from the Ghost raw_type!"
);
return method(name, method_variadic_t<Ret, Args...>(fn));
}
/**
* Create a shared pointer on the heap to handle the reference counting
@@ -806,11 +840,11 @@ namespace nasal
*/
template<class RefType>
static
typename boost::enable_if_c<
boost::is_same<RefType, strong_ref>::value
|| boost::is_same<RefType, weak_ref>::value,
std::enable_if_t<
std::is_same<RefType, strong_ref>::value
|| std::is_same<RefType, weak_ref>::value,
naRef
>::type
>
makeGhost(naContext c, RefType const& ref_ptr)
{
strong_ref ref(ref_ptr);
@@ -848,10 +882,9 @@ namespace nasal
}
// otherwise try the derived classes
for( typename DerivedList::reverse_iterator
derived = getSingletonPtr()->_derived_types.rbegin();
derived != getSingletonPtr()->_derived_types.rend();
++derived )
for( auto derived = getSingletonPtr()->_derived_types.rbegin();
derived != getSingletonPtr()->_derived_types.rend();
++derived )
{
strong_ref ref = (derived->from_nasal)(c, me);
@@ -865,13 +898,10 @@ namespace nasal
if( !naIsVector(na_parents) )
return strong_ref();
typedef std::vector<naRef> naRefs;
naRefs parents = from_nasal<naRefs>(c, na_parents);
for( naRefs::const_iterator parent = parents.begin();
parent != parents.end();
++parent )
auto parents = from_nasal<std::vector<naRef>>(c, na_parents);
for(auto parent: parents)
{
strong_ref ref = fromNasal(c, *parent);
strong_ref ref = fromNasal(c, parent);
if( get_pointer(ref) )
return ref;
}
@@ -923,8 +953,9 @@ namespace nasal
static naGhostType _ghost_type_strong, //!< Stored as shared pointer
_ghost_type_weak; //!< Stored as weak shared pointer
typedef naRef (*to_nasal_t)(naContext, const strong_ref&, bool);
typedef strong_ref (*from_nasal_t)(naContext, naRef);
using to_nasal_t = naRef (*)(naContext, const strong_ref&, bool);
using from_nasal_t = strong_ref (*)(naContext, naRef);
struct DerivedInfo
{
to_nasal_t to_nasal;
@@ -937,7 +968,7 @@ namespace nasal
{}
};
typedef std::vector<DerivedInfo> DerivedList;
using DerivedList = std::vector<DerivedInfo>;
DerivedList _derived_types;
static bool isInstance(naGhostType* ghost_type, bool& is_weak)
@@ -951,13 +982,10 @@ namespace nasal
template<class RefPtr, bool is_weak>
static
typename boost::enable_if_c<
!is_weak,
RefPtr
>::type
std::enable_if_t<!is_weak, RefPtr>
getPtr(void* ptr)
{
typedef shared_ptr_storage<strong_ref> storage_type;
using storage_type = shared_ptr_storage<strong_ref>;
if( ptr )
return storage_type::template get<RefPtr>(
static_cast<typename storage_type::storage_type*>(ptr)
@@ -968,13 +996,13 @@ namespace nasal
template<class RefPtr, bool is_weak>
static
typename boost::enable_if_c<
std::enable_if_t<
is_weak && supports_weak_ref<T>::value,
RefPtr
>::type
>
getPtr(void* ptr)
{
typedef shared_ptr_storage<weak_ref> storage_type;
using storage_type = shared_ptr_storage<weak_ref>;
if( ptr )
return storage_type::template get<RefPtr>(
static_cast<typename storage_type::storage_type*>(ptr)
@@ -985,10 +1013,10 @@ namespace nasal
template<class RefPtr, bool is_weak>
static
typename boost::enable_if_c<
std::enable_if_t<
is_weak && !supports_weak_ref<T>::value,
RefPtr
>::type
>
getPtr(void* ptr)
{
return RefPtr();
@@ -1004,10 +1032,10 @@ namespace nasal
template<class BaseGhost>
static
typename boost::enable_if
< boost::is_polymorphic<typename BaseGhost::raw_type>,
naRef
>::type
std::enable_if_t<
std::is_polymorphic<typename BaseGhost::raw_type>::value,
naRef
>
toNasal( naContext c,
const typename BaseGhost::strong_ref& base_ref,
bool strong )
@@ -1016,10 +1044,10 @@ namespace nasal
// Check first if passed pointer can by converted to instance of class
// this ghost wraps.
if( !boost::is_same
< typename BaseGhost::raw_type,
typename Ghost::raw_type
>::value
if( !std::is_same<
typename BaseGhost::raw_type,
typename Ghost::raw_type
>::value
&& dynamic_cast<const typename Ghost::raw_type*>(ptr) != ptr )
return naNil();
@@ -1038,10 +1066,9 @@ namespace nasal
static_pointer_cast<typename Ghost::raw_type>(base_ref);
// Now check if we can further downcast to one of our derived classes.
for( typename DerivedList::reverse_iterator
derived = getSingletonPtr()->_derived_types.rbegin();
derived != getSingletonPtr()->_derived_types.rend();
++derived )
for( auto derived = getSingletonPtr()->_derived_types.rbegin();
derived != getSingletonPtr()->_derived_types.rend();
++derived )
{
naRef ghost = (derived->to_nasal)(c, ref, strong);
@@ -1058,13 +1085,13 @@ namespace nasal
template<class BaseGhost>
static
typename boost::disable_if
< boost::is_polymorphic<typename BaseGhost::raw_type>,
naRef
>::type
toNasal( naContext c,
const typename BaseGhost::strong_ref& ref,
bool strong )
std::enable_if_t<
!std::is_polymorphic<typename BaseGhost::raw_type>::value,
naRef
>
toNasal( naContext c,
const typename BaseGhost::strong_ref& ref,
bool strong )
{
// For non polymorphic classes there is no possibility to get the actual
// dynamic type, therefore we can only use its static type.
@@ -1087,7 +1114,7 @@ namespace nasal
template<class Ret>
getter_t to_getter(Ret (raw_type::*getter)() const)
{
typedef typename boost::call_traits<Ret>::param_type param_type;
using param_type = typename boost::call_traits<Ret>::param_type;
naRef(*to_nasal_)(naContext, param_type) = &to_nasal;
// Getter signature: naRef(raw_type&, naContext)
@@ -1140,7 +1167,7 @@ namespace nasal
*/
template<class Ret>
static
typename boost::disable_if<boost::is_void<Ret>, naRef>::type
std::enable_if_t<!std::is_void<Ret>::value, naRef>
method_invoker
(
const boost::function<Ret (raw_type&, const CallContext&)>& func,
@@ -1148,7 +1175,7 @@ namespace nasal
const CallContext& ctx
)
{
return (*to_nasal_ptr<Ret>::get())(ctx.c, func(obj, ctx));
return (*to_nasal_ptr<Ret>::get())(ctx.c_ctx(), func(obj, ctx));
};
/**
@@ -1156,7 +1183,7 @@ namespace nasal
*/
template<class Ret>
static
typename boost::enable_if<boost::is_void<Ret>, naRef>::type
std::enable_if_t<std::is_void<Ret>::value, naRef>
method_invoker
(
const boost::function<Ret (raw_type&, const CallContext&)>& func,
@@ -1174,10 +1201,10 @@ namespace nasal
*/
template<class Arg>
static
typename boost::disable_if<
internal::reduced_is_same<Arg, CallContext>,
std::enable_if_t<
!std::is_same<std::remove_cvref_t<Arg>, CallContext>::value,
typename from_nasal_ptr<Arg>::return_type
>::type
>
arg_from_nasal(const CallContext& ctx, size_t index)
{
return ctx.requireArg<Arg>(index);
@@ -1188,19 +1215,21 @@ namespace nasal
*/
template<class Arg>
static
typename boost::enable_if<
internal::reduced_is_same<Arg, CallContext>,
std::enable_if_t<
std::is_same<std::remove_cvref_t<Arg>, CallContext>::value,
typename from_nasal_ptr<Arg>::return_type
>::type
>
arg_from_nasal(const CallContext& ctx, size_t)
{
// Either const CallContext& or CallContext, non-const reference
// does not make sense.
BOOST_STATIC_ASSERT( (!boost::is_same<Arg, CallContext&>::value) );
static_assert(
!boost::is_same<Arg, CallContext&>::value,
"Only const reference and value make sense!");
return ctx;
};
typedef std::unique_ptr<Ghost> GhostPtr;
using GhostPtr = std::unique_ptr<Ghost>;
MemberMap _members;
fallback_getter_t _fallback_getter;
fallback_setter_t _fallback_setter;
@@ -1240,13 +1269,10 @@ namespace nasal
template<bool is_weak>
static
typename boost::enable_if_c<
!is_weak,
naRef
>::type
std::enable_if_t<!is_weak, naRef>
create(naContext c, const strong_ref& ref_ptr)
{
typedef shared_ptr_storage<strong_ref> storage_type;
using storage_type = shared_ptr_storage<strong_ref>;
return naNewGhost2( c,
&Ghost::_ghost_type_strong,
storage_type::ref(ref_ptr) );
@@ -1254,13 +1280,13 @@ namespace nasal
template<bool is_weak>
static
typename boost::enable_if_c<
std::enable_if_t<
is_weak && supports_weak_ref<T>::value,
naRef
>::type
>
create(naContext c, const strong_ref& ref_ptr)
{
typedef shared_ptr_storage<weak_ref> storage_type;
using storage_type = shared_ptr_storage<weak_ref>;
return naNewGhost2( c,
&Ghost::_ghost_type_weak,
storage_type::ref(ref_ptr) );
@@ -1268,10 +1294,10 @@ namespace nasal
template<bool is_weak>
static
typename boost::enable_if_c<
std::enable_if_t<
is_weak && !supports_weak_ref<T>::value,
naRef
>::type
>
create(naContext, const strong_ref&)
{
return naNil();
@@ -1280,7 +1306,7 @@ namespace nasal
template<class Type>
static void destroy(void *ptr)
{
typedef shared_ptr_storage<Type> storage_type;
using storage_type = shared_ptr_storage<Type>;
storage_type::unref(
static_cast<typename storage_type::storage_type*>(ptr)
);
@@ -1322,9 +1348,7 @@ namespace nasal
// return "";
// }
typename MemberMap::iterator member =
getSingletonPtr()->_members.find(key_str);
auto member = getSingletonPtr()->_members.find(key_str);
if( member == getSingletonPtr()->_members.end() )
{
fallback_getter_t fallback_get = getSingletonPtr()->_fallback_getter;
@@ -1374,7 +1398,7 @@ namespace nasal
const std::string key = nasal::from_nasal<std::string>(c, field);
const MemberMap& members = getSingletonPtr()->_members;
typename MemberMap::const_iterator member = members.find(key);
auto member = members.find(key);
if( member == members.end() )
{
fallback_setter_t fallback_set = getSingletonPtr()->_fallback_setter;
@@ -1415,13 +1439,11 @@ namespace nasal
template<class T>
naGhostType
Ghost<T, typename boost::enable_if<is_strong_ref<T> >::type>
::_ghost_type_strong;
Ghost<T, std::enable_if_t<is_strong_ref<T>::value>>::_ghost_type_strong;
template<class T>
naGhostType
Ghost<T, typename boost::enable_if<is_strong_ref<T> >::type>
::_ghost_type_weak;
Ghost<T, std::enable_if_t<is_strong_ref<T>::value>>::_ghost_type_weak;
} // namespace nasal
@@ -1430,15 +1452,13 @@ namespace nasal
* Convert every shared pointer to a ghost.
*/
template<class T>
typename boost::enable_if<
nasal::internal::has_element_type<
typename nasal::internal::reduced_type<T>::type
>,
std::enable_if_t<
nasal::internal::has_element_type<std::remove_cvref_t<T>>::value,
naRef
>::type
>
to_nasal_helper(naContext c, T ptr)
{
typedef typename nasal::shared_ptr_traits<T>::strong_ref strong_ref;
using strong_ref = typename nasal::shared_ptr_traits<T>::strong_ref;
return nasal::Ghost<strong_ref>::makeGhost(c, ptr);
}
@@ -1446,15 +1466,13 @@ to_nasal_helper(naContext c, T ptr)
* Convert nasal ghosts/hashes to shared pointer (of a ghost).
*/
template<class T>
typename boost::enable_if<
nasal::internal::has_element_type<
typename nasal::internal::reduced_type<T>::type
>,
std::enable_if_t<
nasal::internal::has_element_type<std::remove_cvref_t<T>>::value,
T
>::type
>
from_nasal_helper(naContext c, naRef ref, const T*)
{
typedef typename nasal::shared_ptr_traits<T>::strong_ref strong_ref;
using strong_ref = typename nasal::shared_ptr_traits<T>::strong_ref;
return T(nasal::Ghost<strong_ref>::fromNasalChecked(c, ref));
}
@@ -1462,11 +1480,11 @@ from_nasal_helper(naContext c, naRef ref, const T*)
* Convert any pointer to a SGReferenced based object to a ghost.
*/
template<class T>
typename boost::enable_if_c<
boost::is_base_of<SGReferenced, T>::value
|| boost::is_base_of<SGWeakReferenced, T>::value,
std::enable_if_t<
std::is_base_of<SGReferenced, T>::value
|| std::is_base_of<SGWeakReferenced, T>::value,
naRef
>::type
>
to_nasal_helper(naContext c, T* ptr)
{
return nasal::Ghost<SGSharedPtr<T> >::makeGhost(c, SGSharedPtr<T>(ptr));
@@ -1476,20 +1494,14 @@ to_nasal_helper(naContext c, T* ptr)
* Convert nasal ghosts/hashes to pointer (of a SGReferenced based ghost).
*/
template<class T>
typename boost::enable_if_c<
boost::is_base_of<
SGReferenced,
typename boost::remove_pointer<T>::type
>::value
|| boost::is_base_of<
SGWeakReferenced,
typename boost::remove_pointer<T>::type
>::value,
std::enable_if_t<
std::is_base_of<SGReferenced, std::remove_pointer_t<T>>::value
|| std::is_base_of<SGWeakReferenced, std::remove_pointer_t<T>>::value,
T
>::type
>
from_nasal_helper(naContext c, naRef ref, const T*)
{
typedef SGSharedPtr<typename boost::remove_pointer<T>::type> TypeRef;
using TypeRef = SGSharedPtr<std::remove_pointer_t<T>>;
return T(nasal::Ghost<TypeRef>::fromNasalChecked(c, ref));
}
@@ -1497,10 +1509,10 @@ from_nasal_helper(naContext c, naRef ref, const T*)
* Convert any pointer to a osg::Referenced based object to a ghost.
*/
template<class T>
typename boost::enable_if<
boost::is_base_of<osg::Referenced, T>,
std::enable_if_t<
std::is_base_of<osg::Referenced, T>::value,
naRef
>::type
>
to_nasal_helper(naContext c, T* ptr)
{
return nasal::Ghost<osg::ref_ptr<T> >::makeGhost(c, osg::ref_ptr<T>(ptr));
@@ -1510,13 +1522,13 @@ to_nasal_helper(naContext c, T* ptr)
* Convert nasal ghosts/hashes to pointer (of a osg::Referenced based ghost).
*/
template<class T>
typename boost::enable_if<
boost::is_base_of<osg::Referenced, typename boost::remove_pointer<T>::type>,
std::enable_if_t<
std::is_base_of<osg::Referenced, std::remove_pointer_t<T>>::value,
T
>::type
>
from_nasal_helper(naContext c, naRef ref, const T*)
{
typedef osg::ref_ptr<typename boost::remove_pointer<T>::type> TypeRef;
using TypeRef = osg::ref_ptr<std::remove_pointer_t<T>>;
return T(nasal::Ghost<TypeRef>::fromNasalChecked(c, ref));
}

View File

@@ -20,8 +20,7 @@
#ifndef SG_NASAL_CALL_CONTEXT_HXX_
#define SG_NASAL_CALL_CONTEXT_HXX_
#include "from_nasal.hxx"
#include "to_nasal.hxx"
#include "NasalContext.hxx"
namespace nasal
{
@@ -29,11 +28,12 @@ namespace nasal
/**
* Context passed to a function/method being called from Nasal
*/
class CallContext
class CallContext:
public ContextWrapper
{
public:
CallContext(naContext c, naRef me, size_t argc, naRef* args):
c(c),
ContextWrapper(c),
me(me),
argc(argc),
args(args)
@@ -102,28 +102,14 @@ namespace nasal
requireArg(size_t index) const
{
if( index >= argc )
naRuntimeError(c, "Missing required arg #%d", index);
runtimeError("Missing required arg #%d", index);
return from_nasal<T>(args[index]);
}
template<class T>
naRef to_nasal(T arg) const
{
return nasal::to_nasal(c, arg);
}
template<class T>
typename from_nasal_ptr<T>::return_type
from_nasal(naRef ref) const
{
return (*from_nasal_ptr<T>::get())(c, ref);
}
naContext c;
naRef me;
size_t argc;
naRef *args;
naRef me;
size_t argc;
naRef *args;
};
} // namespace nasal

View File

@@ -17,13 +17,79 @@
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "NasalContext.hxx"
#include "NasalHash.hxx"
#include "NasalString.hxx"
#include <cassert>
namespace nasal
{
//----------------------------------------------------------------------------
ContextWrapper::ContextWrapper(naContext ctx):
_ctx(ctx)
{
assert(_ctx);
}
//----------------------------------------------------------------------------
ContextWrapper::operator naContext()
{
return _ctx;
}
//----------------------------------------------------------------------------
naContext ContextWrapper::c_ctx() const
{
return const_cast<naContext>(_ctx);
}
//----------------------------------------------------------------------------
Hash ContextWrapper::newHash()
{
return Hash(_ctx);
}
//----------------------------------------------------------------------------
String ContextWrapper::newString(const char* str)
{
return String(_ctx, str);
}
//----------------------------------------------------------------------------
naRef ContextWrapper::callMethod( Me me,
naRef code,
std::initializer_list<naRef> args )
{
naRef ret = naCallMethodCtx(
_ctx,
code,
me,
args.size(),
const_cast<naRef*>(args.begin()),
naNil() // locals
);
if( const char* error = naGetError(_ctx) )
throw std::runtime_error(error);
return ret;
}
//----------------------------------------------------------------------------
naRef ContextWrapper::newVector(std::initializer_list<naRef> vals)
{
naRef vec = naNewVector(_ctx);
naVec_setsize(_ctx, vec, vals.size());
int i = 0;
for(naRef val: vals)
naVec_set(vec, i++, val);
return vec;
}
//----------------------------------------------------------------------------
Context::Context():
_ctx(naNewContext())
ContextWrapper(naNewContext())
{
}
@@ -32,18 +98,7 @@ namespace nasal
Context::~Context()
{
naFreeContext(_ctx);
}
//----------------------------------------------------------------------------
Context::operator naContext()
{
return _ctx;
}
//----------------------------------------------------------------------------
Hash Context::newHash()
{
return Hash(_ctx);
_ctx = nullptr;
}
} // namespace nasal

View File

@@ -19,28 +19,110 @@
#ifndef SG_NASAL_CONTEXT_HXX_
#define SG_NASAL_CONTEXT_HXX_
#include "NasalHash.hxx"
#include "cppbind_fwd.hxx"
#include "NasalMe.hxx"
#include <boost/call_traits.hpp>
#include <initializer_list>
namespace nasal
{
/**
* Manage lifetime and encapsulate a Nasal context.
* Wraps a nasal ::naContext without taking ownership/managing its lifetime
*/
class Context
class ContextWrapper
{
public:
explicit ContextWrapper(naContext ctx);
operator naContext();
/** Convert to non-const ::naContext for usage with C-APIs */
naContext c_ctx() const;
Hash newHash();
String newString(const char* str);
/** Raise a nasal runtime error */
template<class... Args>
void runtimeError(const char* fmt, Args ... args) const
{
naRuntimeError(c_ctx(), fmt, args...);
}
template<class T>
naRef to_nasal(T arg) const
{
return nasal::to_nasal(_ctx, arg);
}
template<class T, std::size_t N>
naRef to_nasal(const T(&array)[N]) const
{
return nasal::to_nasal(_ctx, array);
}
/** Create a nasal vector filled with the given values */
template<class... Vals>
naRef to_nasal_vec(Vals ... vals)
{
return newVector({to_nasal(vals)...});
}
template<class T>
Me to_me(T arg) const
{
return Me{ to_nasal(arg) };
}
template<class T>
typename from_nasal_ptr<T>::return_type
from_nasal(naRef ref) const
{
return (*from_nasal_ptr<T>::get())(_ctx, ref);
}
naRef callMethod(Me me, naRef code, std::initializer_list<naRef> args);
template<class Ret, class... Args>
Ret callMethod( Me me,
naRef code,
typename boost::call_traits<Args>::param_type ... args )
{
// TODO warn if with Ret == void something different to nil is returned?
return from_nasal<Ret>(callMethod(
me,
code,
{ to_nasal<typename boost::call_traits<Args>::param_type>(args)... }
));
}
protected:
naContext _ctx;
// Not exposed to avoid confusion of intializer_list<naRef> to variadic
// arguments
naRef newVector(std::initializer_list<naRef> vals);
};
/**
* Creates and manages the lifetime of a ::naContext
*/
class Context:
public ContextWrapper
{
public:
Context();
~Context();
operator naContext();
Hash newHash();
protected:
naContext _ctx;
Context(const Context&) = delete;
Context& operator=(const Context&) = delete;
};
} // namespace nasal
#include "from_nasal.hxx"
#include "to_nasal.hxx"
#endif /* SG_NASAL_CONTEXT_HXX_ */

View File

@@ -0,0 +1,44 @@
///@file
//
// Copyright (C) 2018 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_NASAL_ME_HXX_
#define SG_NASAL_ME_HXX_
#include <simgear/nasal/nasal.h>
namespace nasal
{
/**
* Wrap a naRef to indicate it references the self/me object in Nasal method
* calls.
*/
struct Me
{
naRef _ref;
explicit Me(naRef ref = naNil()):
_ref(ref)
{}
operator naRef() { return _ref; }
};
} // namespace nasal
#endif /* SG_NASAL_ME_HXX_ */

View File

@@ -0,0 +1,75 @@
///@file
//
// Copyright (C) 2018 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_NASAL_METHOD_HOLDER_HXX_
#define SG_NASAL_METHOD_HOLDER_HXX_
#include "NasalContext.hxx"
#include "NasalObjectHolder.hxx"
namespace nasal
{
/**
* Hold any callable function in Nasal and call it from C++
*/
template<class Ret, class... Args>
class NasalMethodHolder
{
using Holder = ObjectHolder<SGReferenced>;
public:
NasalMethodHolder(naRef code):
_code(Holder::makeShared(code))
{}
/**
* @brief Call the function with the given arguments
*
* If the first argument is nasal::Me it will be passed as 'me' object and
* not as argument.
*/
Ret operator()(Args ... args)
{
return call(args...);
}
private:
Holder::Ref _code;
template<class... CArgs>
Ret call(Me self, CArgs ... args)
{
nasal::Context ctx;
return ctx.callMethod<Ret, CArgs...>(
self,
_code->get_naRef(),
args...
);
}
template<class... CArgs>
Ret call(CArgs ... args)
{
return call(Me{}, args...);
}
};
} // namespace nasal
#endif /* SG_NASAL_METHOD_HOLDER_HXX_ */

View File

@@ -23,9 +23,6 @@
#include "NasalObjectHolder.hxx"
#include "Ghost.hxx"
#include <boost/preprocessor/iteration/iterate.hpp>
#include <boost/preprocessor/repetition/enum_trailing_binary_params.hpp>
namespace nasal
{
/**
@@ -48,14 +45,21 @@ namespace nasal
bool valid() const;
// Build dependency for CMake, gcc, etc.
#define SG_DONT_DO_ANYTHING
# include <simgear/nasal/cppbind/detail/NasalObject_callMethod_templates.hxx>
#undef SG_DONT_DO_ANYTHING
template<class Ret, class... Args>
Ret callMethod(const std::string& name, Args ... args)
{
if( !_nasal_impl.valid() )
return Ret();
#define BOOST_PP_ITERATION_LIMITS (0, 9)
#define BOOST_PP_FILENAME_1 <simgear/nasal/cppbind/detail/NasalObject_callMethod_templates.hxx>
#include BOOST_PP_ITERATE()
Context ctx;
auto func = get_member<boost::function<Ret (Me, Args...)>>(
ctx, _nasal_impl.get_naRef(), name
);
if( func )
return func(Me(ctx.to_nasal(this)), args...);
return Ret();
}
bool _set(naContext c, const std::string& key, naRef val);
bool _get(naContext c, const std::string& key, naRef& out);

View File

@@ -42,6 +42,8 @@ namespace nasal
{
public:
using Ref = SGSharedPtr<ObjectHolder<Base>>;
/**
* @param obj Object to save
*/
@@ -84,7 +86,7 @@ namespace nasal
*
* @param obj Object to save
*/
static SGSharedPtr<ObjectHolder<Base> > makeShared(naRef obj);
static Ref makeShared(naRef obj);
protected:
naRef _ref;
@@ -139,10 +141,9 @@ namespace nasal
template<class Base>
ObjectHolder<Base>::ObjectHolder(naRef obj):
_ref(obj),
_gc_key(0)
_gc_key(naIsNil(obj) ? 0 : naGCSave(obj))
{
if( !naIsNil(obj) )
naGCSave(obj);
}
//----------------------------------------------------------------------------
@@ -156,10 +157,10 @@ namespace nasal
//----------------------------------------------------------------------------
template<class Base>
SGSharedPtr<ObjectHolder<Base> >
typename ObjectHolder<Base>::Ref
ObjectHolder<Base>::makeShared(naRef obj)
{
return SGSharedPtr<ObjectHolder<Base> >( new ObjectHolder<SGReferenced>(obj) );
return Ref( new ObjectHolder<Base>(obj) );
}
} // namespace nasal

View File

@@ -47,7 +47,7 @@ namespace nasal
*
* @param str Existing Nasal String
*/
String(naRef str);
explicit String(naRef str);
const char* c_str() const;
const char* begin() const;

View File

@@ -0,0 +1,60 @@
///@file
/// Nasal C++ Bindings forward declarations
///
// Copyright (C) 2018 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_NASAL_CPPBIND_FWD_HXX_
#define SG_NASAL_CPPBIND_FWD_HXX_
#include <simgear/nasal/nasal.h>
#include <cstddef>
#include <string>
namespace nasal
{
class bad_nasal_cast;
class CallContext;
class Context;
class ContextWrapper;
class Hash;
struct Me;
class Object;
class String;
template<class, class>
class Ghost;
template<class T>
naRef to_nasal(naContext c, T arg);
template<class T, std::size_t N>
naRef to_nasal(naContext c, const T(&array)[N]);
template<class T>
T from_nasal(naContext c, naRef ref);
template<class Var>
struct from_nasal_ptr;
template<class T>
T get_member(naContext c, naRef obj, const std::string& name);
} // namespace nasal
#endif /* SG_NASAL_CPPBIND_FWD_HXX_ */

View File

@@ -1,35 +0,0 @@
#ifndef SG_NASAL_OBJECT_HXX_
# error Nasal cppbind - do not include this file!
#endif
#ifndef SG_DONT_DO_ANYTHING
#define n BOOST_PP_ITERATION()
#define SG_CALL_ARG(z, n, dummy)\
to_nasal<typename boost::call_traits<A##n>::param_type>(ctx, a##n)
template<
class Ret
BOOST_PP_ENUM_TRAILING_PARAMS(n, class A)
>
Ret callMethod( const std::string& name
BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(n, A, a) )
{
if( !_nasal_impl.valid() )
return Ret();
typedef boost::function<Ret (nasal::Me BOOST_PP_ENUM_TRAILING_PARAMS(n, A))>
MemFunc;
Context ctx;
MemFunc f = get_member<MemFunc>(ctx, _nasal_impl.get_naRef(), name.c_str());
if( f )
return f(nasal::to_nasal(ctx, this) BOOST_PP_ENUM_TRAILING_PARAMS(n, a));
return Ret();
}
#undef SG_CALL_ARG
#undef n
#endif // SG_DONT_DO_ANYTHING

View File

@@ -1,131 +0,0 @@
#ifndef SG_FROM_NASAL_HELPER_HXX_
# error Nasal cppbind - do not include this file!
#endif
#ifndef SG_DONT_DO_ANYTHING
#define n BOOST_PP_ITERATION()
#ifndef SG_BOOST_FUNCTION_FROM_NASAL_FWD
# define SG_CALL_TRAITS_PARAM(z, n, dummy)\
typename boost::call_traits<A##n>::param_type a##n
# define SG_CALL_ARG(z, n, dummy)\
to_nasal<typename boost::call_traits<A##n>::param_type>(ctx, a##n)
template<
class Ret
BOOST_PP_ENUM_TRAILING_PARAMS(n, class A)
>
typename boost::disable_if<boost::is_void<Ret>, Ret>::type
callNasalMethod( const ObjectHolder<SGReferenced>* holder,
Me self
BOOST_PP_ENUM_TRAILING(n, SG_CALL_TRAITS_PARAM, 0) )
{
naContext ctx = naNewContext();
#if n
naRef args[n] = {
BOOST_PP_ENUM(n, SG_CALL_ARG, 0)
};
#else
naRef* args = NULL;
#endif
naRef result =
naCallMethodCtx(ctx, holder->get_naRef(), self, n, args, naNil());
const char* error = naGetError(ctx);
std::string error_str(error ? error : "");
Ret r = Ret();
if( !error )
r = from_nasal_helper(ctx, result, static_cast<Ret*>(0));
naFreeContext(ctx);
if( error )
throw std::runtime_error(error_str);
return r;
}
template<
class Ret
BOOST_PP_ENUM_TRAILING_PARAMS(n, class A)
>
typename boost::enable_if<boost::is_void<Ret>, Ret>::type
callNasalMethod( const ObjectHolder<SGReferenced>* holder,
Me self
BOOST_PP_ENUM_TRAILING(n, SG_CALL_TRAITS_PARAM, 0) )
{
callNasalMethod<
naRef // do not do any conversion and just ignore the return value
// TODO warn if something different to nil is returned?
BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM_PARAMS(n, A)
>
(
holder,
self
BOOST_PP_ENUM_TRAILING_PARAMS(n, a)
);
}
# undef SG_CALL_TRAITS_PARAM
# undef SG_CALL_ARG
#endif
template<
class Ret
BOOST_PP_ENUM_TRAILING_PARAMS(n, class A)
>
typename boost::disable_if<
// free function if first argument is not nasal::Me or no argument at all
boost::is_same<BOOST_PP_IF(n, A0, void), Me>,
boost::function<Ret (BOOST_PP_ENUM_PARAMS(n, A))>
>::type
boostFunctionFromNasal(naRef code, Ret (*)(BOOST_PP_ENUM_PARAMS(n, A)))
#ifdef SG_BOOST_FUNCTION_FROM_NASAL_FWD
;
#else
{
return boost::bind
(
&callNasalMethod<Ret BOOST_PP_ENUM_TRAILING_PARAMS(n, A)>,
ObjectHolder<SGReferenced>::makeShared(code),
boost::bind(naNil)
BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM_SHIFTED_PARAMS(BOOST_PP_INC(n), _)
);
}
#endif
#if n > 0
template<
class Ret
BOOST_PP_ENUM_TRAILING_PARAMS(n, class A)
>
typename boost::enable_if<
// method if type of first argument is nasal::Me
boost::is_same<A0, Me>,
boost::function<Ret (BOOST_PP_ENUM_PARAMS(n, A))>
>::type
boostFunctionFromNasal(naRef code, Ret (*)(BOOST_PP_ENUM_PARAMS(n, A)))
#ifdef SG_BOOST_FUNCTION_FROM_NASAL_FWD
;
#else
{
return boost::bind
(
&callNasalMethod<
Ret
BOOST_PP_COMMA_IF(BOOST_PP_DEC(n))
BOOST_PP_ENUM_SHIFTED_PARAMS(n, A)
>,
ObjectHolder<SGReferenced>::makeShared(code),
BOOST_PP_ENUM_SHIFTED_PARAMS(BOOST_PP_INC(n), _)
);
}
#endif
#endif
#undef n
#endif // SG_DONT_DO_ANYTHING

View File

@@ -39,7 +39,7 @@ namespace nasal
}
//----------------------------------------------------------------------------
bad_nasal_cast::~bad_nasal_cast() throw()
bad_nasal_cast::~bad_nasal_cast()
{
}

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