Compare commits

..

30 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@@ -249,8 +249,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 +264,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 +282,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)

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

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

View File

@@ -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,14 @@
include (SimGearComponent)
set(HEADERS EmbeddedResource.hxx EmbeddedResourceManager.hxx)
set(SOURCES EmbeddedResource.cxx EmbeddedResourceManager.cxx)
simgear_component(embedded_resources embedded_resources
"${SOURCES}" "${HEADERS}")
if(ENABLE_TESTS)
add_executable(test_embedded_resources embedded_resources_test.cxx)
target_link_libraries(test_embedded_resources ${TEST_LIBS})
add_test(embedded_resources
${EXECUTABLE_OUTPUT_PATH}/test_embedded_resources)
endif(ENABLE_TESTS)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

@@ -1054,3 +1054,23 @@ 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 {};
}
}

View File

@@ -281,6 +281,11 @@ public:
*/
SGPath dirPath() const;
/*
* return path as a file:// URI
*/
std::string fileUrl() const;
enum StandardLocation
{
HOME,

View File

@@ -14,6 +14,7 @@ set(SOURCES
Package.cxx
Install.cxx
Root.cxx
Delegate.cxx
# internal helpers
md5.h md5.c
ioapi.c ioapi_mem.c ioapi.h

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2017 James Turner - zakalawe@mac.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.
//
#include <simgear/package/Delegate.hxx>
#include <simgear/package/Install.hxx>
#include <simgear/package/Package.hxx>
#include <simgear/package/Catalog.hxx>
namespace simgear
{
namespace pkg
{
void Delegate::installStatusChanged(InstallRef aInstall, StatusCode aReason)
{
}
void Delegate::dataForThumbnail(const std::string& aThumbnailUrl,
size_t lenth, const uint8_t* bytes)
{
}
} // of namespace pkg
} // of namespace simgear

View File

@@ -18,6 +18,7 @@
#ifndef SG_PACKAGE_DELEGATE_HXX
#define SG_PACKAGE_DELEGATE_HXX
#include <string>
#include <simgear/misc/stdint.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
@@ -52,6 +53,7 @@ public:
FAIL_FILESYSTEM, ///< unknown filesystem error occurred
FAIL_VERSION, ///< version check mismatch
FAIL_NOT_FOUND, ///< package URL returned a 404
FAIL_HTTP_FORBIDDEN, ///< URL returned a 403. Marked specially to catch rate-limiting
STATUS_REFRESHED,
USER_CANCELLED
} StatusCode;
@@ -76,9 +78,15 @@ public:
* Notification when catalogs/packages are added or removed
*/
virtual void availablePackagesChanged() {}
/**
* More general purpose notification when install is queued / cancelled / started
* stopped. Reason value is only in certain cases.
*/
virtual void installStatusChanged(InstallRef aInstall, StatusCode aReason);
virtual void dataForThumbnail(const std::string& aThumbnailUrl,
size_t lenth, const uint8_t* bytes) {}
virtual void dataForThumbnail(const std::string& aThumbnailUrl,
size_t lenth, const uint8_t* bytes);
};
} // of namespace pkg

View File

@@ -268,6 +268,11 @@ string_set Package::tags() const
{
return m_tags;
}
bool Package::hasTag(const std::string& tag) const
{
return m_tags.find(tag) != m_tags.end();
}
SGPropertyNode* Package::properties() const
{

View File

@@ -142,6 +142,13 @@ public:
string_set tags() const;
/**
* @brief hasTag - efficently check if a tag is defined or not
* @param tag
* @return
*/
bool hasTag(const std::string& tag) const;
/**
* download URLs for the package
*/

View File

@@ -30,27 +30,50 @@
#include <simgear/io/HTTPRequest.hxx>
#include <simgear/io/HTTPClient.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/sg_hash.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/package/Package.hxx>
#include <simgear/package/Install.hxx>
#include <simgear/package/Catalog.hxx>
const int SECONDS_PER_DAY = 24 * 60 * 60;
namespace simgear {
namespace {
std::string hashForUrl(const std::string& d)
{
sha1nfo info;
sha1_init(&info);
sha1_write(&info, d.data(), d.size());
return strutils::encodeHex(sha1_result(&info), HASH_LENGTH);
}
} // of anonymous namespace
namespace pkg {
typedef std::map<std::string, CatalogRef> CatalogDict;
typedef std::vector<Delegate*> DelegateVec;
typedef std::map<std::string, std::string> MemThumbnailCache;
typedef std::deque<std::string> StringDeque;
class Root::ThumbnailDownloader : public HTTP::Request
{
public:
ThumbnailDownloader(Root::RootPrivate* aOwner, const std::string& aUrl) :
HTTP::Request(aUrl),
m_owner(aOwner)
ThumbnailDownloader(Root::RootPrivate* aOwner,
const std::string& aUrl, const std::string& aRealUrl = std::string()) :
HTTP::Request(aUrl),
m_owner(aOwner),
m_realUrl(aRealUrl)
{
if (m_realUrl.empty()) {
m_realUrl = aUrl;
}
}
std::string realUrl() const
{
return m_realUrl;
}
protected:
@@ -64,6 +87,7 @@ protected:
private:
Root::RootPrivate* m_owner;
std::string m_buffer;
std::string m_realUrl;
};
class Root::RootPrivate
@@ -71,69 +95,90 @@ class Root::RootPrivate
public:
RootPrivate() :
http(NULL),
maxAgeSeconds(60 * 60 * 24)
maxAgeSeconds(SECONDS_PER_DAY)
{
}
void fireStatusChange(InstallRef install, Delegate::StatusCode status)
{
for (auto d : delegates) {
d->installStatusChanged(install, status);
}
}
void fireStartInstall(InstallRef install)
{
DelegateVec::const_iterator it;
for (it = delegates.begin(); it != delegates.end(); ++it) {
(*it)->startInstall(install);
for (auto d : delegates) {
d->startInstall(install);
d->installStatusChanged(install, Delegate::STATUS_IN_PROGRESS);
}
}
void fireInstallProgress(InstallRef install,
unsigned int aBytes, unsigned int aTotal)
{
DelegateVec::const_iterator it;
for (it = delegates.begin(); it != delegates.end(); ++it) {
(*it)->installProgress(install, aBytes, aTotal);
for (auto d : delegates) {
d->installProgress(install, aBytes, aTotal);
}
}
void fireFinishInstall(InstallRef install, Delegate::StatusCode status)
{
DelegateVec::const_iterator it;
for (it = delegates.begin(); it != delegates.end(); ++it) {
(*it)->finishInstall(install, status);
for (auto d : delegates) {
d->finishInstall(install, status);
d->installStatusChanged(install, status);
}
}
void fireRefreshStatus(CatalogRef catalog, Delegate::StatusCode status)
{
DelegateVec::const_iterator it;
for (it = delegates.begin(); it != delegates.end(); ++it) {
(*it)->catalogRefreshed(catalog, status);
for (auto d : delegates) {
d->catalogRefreshed(catalog, status);
}
}
void firePackagesChanged()
{
DelegateVec::const_iterator it;
for (it = delegates.begin(); it != delegates.end(); ++it) {
(*it)->availablePackagesChanged();
}
for (auto d : delegates) {
d->availablePackagesChanged();
}
}
void thumbnailDownloadComplete(HTTP::Request_ptr request,
Delegate::StatusCode status, const std::string& bytes)
{
std::string u(request->url());
auto dl = static_cast<Root::ThumbnailDownloader*>(request.get());
std::string u = dl->realUrl();
if (status == Delegate::STATUS_SUCCESS) {
thumbnailCache[u] = bytes;
fireDataForThumbnail(u, bytes);
thumbnailCache[u].requestPending = false;
fireDataForThumbnail(u, reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size());
// if this was a network load, rather than a re-load from the disk cache,
// then persist to disk now.
if (strutils::starts_with(request->url(), "http")) {
addToPersistentCache(u, bytes);
}
} else if (status == Delegate::FAIL_HTTP_FORBIDDEN) {
// treat this as rate-limiting failure, at least from some mirrors
// (eg Ibiblio) and retry up to the max count
const int retries = (thumbnailCache[u].retryCount++);
if (retries < 3) {
SG_LOG(SG_IO, SG_INFO, "Download failed for: " << u << ", will retry");
thumbnailCache[u].requestPending = true;
pendingThumbnails.push_back(u);
}
} else {
// any other failure.
thumbnailCache[u].requestPending = false;
}
downloadNextPendingThumbnail();
}
void fireDataForThumbnail(const std::string& aUrl, const std::string& bytes)
void fireDataForThumbnail(const std::string& aUrl, const uint8_t* bytes, size_t size)
{
DelegateVec::const_iterator it;
const uint8_t* data = reinterpret_cast<const uint8_t*>(bytes.data());
for (it = delegates.begin(); it != delegates.end(); ++it) {
(*it)->dataForThumbnail(aUrl, bytes.size(), data);
for (auto d : delegates) {
d->dataForThumbnail(aUrl, size, bytes);
}
}
@@ -157,10 +202,78 @@ public:
void fireFinishUninstall(PackageRef pkg)
{
std::for_each(delegates.begin(), delegates.end(),
[pkg](Delegate* d) {d->finishUninstall(pkg);});
for (auto d : delegates) {
d->finishUninstall(pkg);
}
}
void addToPersistentCache(const std::string& url, const std::string& imageBytes)
{
std::string hash = hashForUrl(url);
// append the correct file suffix
auto pos = url.rfind('.');
if (pos == std::string::npos) {
return;
}
SGPath cachePath = path / "ThumbnailCache" / (hash + url.substr(pos));
sg_ofstream fstream(cachePath, std::ios::out | std::ios::trunc | std::ios::binary);
fstream.write(imageBytes.data(), imageBytes.size());
fstream.close();
}
bool checkPersistentCache(const std::string& url)
{
std::string hash = hashForUrl(url);
// append the correct file suffix
auto pos = url.rfind('.');
if (pos == std::string::npos) {
return false;
}
SGPath cachePath = path / "ThumbnailCache" / (hash + url.substr(pos));
if (!cachePath.exists()) {
return false;
}
// check age, if it's too old, expire and download again
int age = time(nullptr) - cachePath.modTime();
if (age > SECONDS_PER_DAY * 7) { // cache for seven days
SG_LOG(SG_IO, SG_INFO, "expiring old cached thumbnail " << url);
cachePath.remove();
return false;
}
queueLoadFromPersistentCache(url, cachePath);
return true;
}
void queueLoadFromPersistentCache(const std::string& url, const SGPath& path)
{
assert(path.exists());
auto it = thumbnailCache.find(url);
if (it == thumbnailCache.end()) {
ThumbnailCacheEntry entry;
entry.pathOnDisk = path;
it = thumbnailCache.insert(it, std::make_pair(url, entry));
} else {
assert(it->second.pathOnDisk == path);
}
if (it->second.requestPending) {
return; // all done
}
it->second.requestPending = true;
auto dl = new Root::ThumbnailDownloader(this, path.fileUrl(), url);
if (http) {
http->makeRequest(dl);
} else {
httpPendingRequests.push_back(dl);
}
}
DelegateVec delegates;
SGPath path;
@@ -178,7 +291,15 @@ public:
HTTP::Request_ptr thumbnailDownloadRequest;
StringDeque pendingThumbnails;
MemThumbnailCache thumbnailCache;
struct ThumbnailCacheEntry
{
int retryCount = 0;
bool requestPending = false;
SGPath pathOnDisk;
};
std::map<std::string, ThumbnailCacheEntry> thumbnailCache;
typedef std::map<PackageRef, InstallRef> InstallCache;
InstallCache m_installs;
@@ -187,16 +308,19 @@ public:
void Root::ThumbnailDownloader::onDone()
{
if (simgear::strutils::starts_with(url(), "file://")) {
m_owner->thumbnailDownloadComplete(this, Delegate::STATUS_SUCCESS, m_buffer);
return;
}
if (responseCode() != 200) {
SG_LOG(SG_GENERAL, SG_ALERT, "thumbnail download failure:" << url());
m_owner->thumbnailDownloadComplete(this, Delegate::FAIL_DOWNLOAD, std::string());
auto status = (responseCode() == 403) ? Delegate::FAIL_HTTP_FORBIDDEN : Delegate::FAIL_DOWNLOAD;
SG_LOG(SG_NETWORK, SG_INFO, "thumbnail download failure: " << url() << " with reason " << responseCode());
m_owner->thumbnailDownloadComplete(this, status, std::string());
return;
}
m_owner->thumbnailDownloadComplete(this, Delegate::STATUS_SUCCESS, m_buffer);
//time(&m_owner->m_retrievedTime);
//m_owner->writeTimestamp();
//m_owner->refreshComplete(Delegate::STATUS_REFRESHED);
}
SGPath Root::path() const
@@ -260,10 +384,14 @@ Root::Root(const SGPath& aPath, const std::string& aVersion) :
Dir dir(aPath);
if (!dir.exists()) {
dir.create(0755);
return;
}
BOOST_FOREACH(SGPath c, dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
Dir thumbsCacheDir(aPath / "ThumbnailCache");
if (!thumbsCacheDir.exists()) {
thumbsCacheDir.create(0755);
}
for (SGPath c : dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
CatalogRef cat = Catalog::createFromPath(this, c);
if (cat) {
if (cat->status() == Delegate::STATUS_SUCCESS) {
@@ -446,7 +574,7 @@ void Root::scheduleToUpdate(InstallRef aInstall)
}
PackageList deps = aInstall->package()->dependencies();
BOOST_FOREACH(Package* dep, deps) {
for (Package* dep : deps) {
// will internally schedule for update if required
// hence be careful, this method is re-entered in here!
dep->install();
@@ -455,6 +583,7 @@ void Root::scheduleToUpdate(InstallRef aInstall)
bool wasEmpty = d->updateDeque.empty();
d->updateDeque.push_back(aInstall);
d->fireStatusChange(aInstall, Delegate::STATUS_IN_PROGRESS);
if (wasEmpty) {
aInstall->startUpdate();
}
@@ -462,8 +591,7 @@ void Root::scheduleToUpdate(InstallRef aInstall)
bool Root::isInstallQueued(InstallRef aInstall) const
{
RootPrivate::UpdateDeque::const_iterator it =
std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
return (it != d->updateDeque.end());
}
@@ -505,11 +633,12 @@ void Root::finishInstall(InstallRef aInstall, Delegate::StatusCode aReason)
void Root::cancelDownload(InstallRef aInstall)
{
RootPrivate::UpdateDeque::iterator it =
std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
if (it != d->updateDeque.end()) {
bool startNext = (aInstall == d->updateDeque.front());
d->updateDeque.erase(it);
d->fireStatusChange(aInstall, Delegate::USER_CANCELLED);
if (startNext) {
if (!d->updateDeque.empty()) {
d->updateDeque.front()->startUpdate();
@@ -609,17 +738,21 @@ bool Root::removeCatalogById(const std::string& aId)
void Root::requestThumbnailData(const std::string& aUrl)
{
MemThumbnailCache::iterator it = d->thumbnailCache.find(aUrl);
auto it = d->thumbnailCache.find(aUrl);
if (it == d->thumbnailCache.end()) {
// insert into cache to mark as pending
d->pendingThumbnails.push_front(aUrl);
d->thumbnailCache[aUrl] = std::string();
d->downloadNextPendingThumbnail();
} else if (!it->second.empty()) {
// already loaded, fire data synchronously
d->fireDataForThumbnail(aUrl, it->second);
bool cachedOnDisk = d->checkPersistentCache(aUrl);
if (cachedOnDisk) {
// checkPersistentCache will insert the entry and schedule
} else {
d->pendingThumbnails.push_front(aUrl);
d->thumbnailCache[aUrl] = RootPrivate::ThumbnailCacheEntry();
d->thumbnailCache[aUrl].requestPending = true;
d->downloadNextPendingThumbnail();
}
} else {
// in cache but empty data, still fetching
if (!it->second.requestPending && it->second.pathOnDisk.exists()) {
d->queueLoadFromPersistentCache(aUrl, it->second.pathOnDisk);
}
}
}

View File

@@ -32,7 +32,7 @@
#include <map>
#include <queue>
#include <utility>
#include <boost/tr1/unordered_map.hpp>
#include <unordered_map>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
@@ -89,36 +89,17 @@ using namespace osgUtil;
using namespace effect;
class UniformFactoryImpl {
public:
ref_ptr<Uniform> getUniform( Effect * effect,
const string & name,
Uniform::Type uniformType,
SGConstPropertyNode_ptr valProp,
const SGReaderWriterOptions* options );
void updateListeners( SGPropertyNode* propRoot );
void addListener(DeferredPropertyListener* listener);
private:
// Default names for vector property components
static const char* vec3Names[];
static const char* vec4Names[];
SGMutex _mutex;
typedef boost::tuple<std::string, Uniform::Type, std::string, std::string> UniformCacheKey;
typedef boost::tuple<ref_ptr<Uniform>, SGPropertyChangeListener*> UniformCacheValue;
std::map<UniformCacheKey,ref_ptr<Uniform> > uniformCache;
typedef std::queue<DeferredPropertyListener*> DeferredListenerList;
DeferredListenerList deferredListenerList;
};
const char* UniformFactoryImpl::vec3Names[] = {"x", "y", "z"};
const char* UniformFactoryImpl::vec4Names[] = {"x", "y", "z", "w"};
ref_ptr<Uniform> UniformFactoryImpl::getUniform( Effect * effect,
const string & name,
Uniform::Type uniformType,
void UniformFactoryImpl::reset()
{
uniformCache.clear();
}
ref_ptr<Uniform> UniformFactoryImpl::getUniform( Effect * effect,
const string & name,
Uniform::Type uniformType,
SGConstPropertyNode_ptr valProp,
const SGReaderWriterOptions* options )
{
@@ -223,8 +204,6 @@ void UniformFactoryImpl::updateListeners( SGPropertyNode* propRoot )
}
}
typedef Singleton<UniformFactoryImpl> UniformFactory;
Effect::Effect()
: _cache(0), _isRealized(false)
@@ -832,13 +811,13 @@ size_t hash_value(const ProgramKey& key)
// XXX Should these be protected by a mutex? Probably
typedef tr1::unordered_map<ProgramKey, ref_ptr<Program>,
typedef std::unordered_map<ProgramKey, ref_ptr<Program>,
boost::hash<ProgramKey>, ProgramKey::EqualTo>
ProgramMap;
ProgramMap programMap;
ProgramMap resolvedProgramMap; // map with resolved shader file names
typedef tr1::unordered_map<ShaderKey, ref_ptr<Shader>, boost::hash<ShaderKey> >
typedef std::unordered_map<ShaderKey, ref_ptr<Shader>, boost::hash<ShaderKey> >
ShaderMap;
ShaderMap shaderMap;

View File

@@ -19,9 +19,11 @@
#include <vector>
#include <string>
#include <boost/tr1/unordered_map.hpp>
#include <unordered_map>
#include <queue>
#include <boost/functional/hash.hpp>
#include <boost/tuple/tuple.hpp>
#include <osg/Object>
#include <osg/observer_ptr>
@@ -29,6 +31,9 @@
#include <simgear/props/props.hxx>
#include <simgear/scene/util/UpdateOnceCallback.hxx>
#include <simgear/threads/SGThread.hxx>
#include <simgear/threads/SGGuard.hxx>
#include <simgear/structure/Singleton.hxx>
namespace osg
{
@@ -48,6 +53,9 @@ class Technique;
class Effect;
class SGReaderWriterOptions;
using namespace osg;
using namespace std;
/**
* Object to be initialized at some point after an effect -- and its
* containing effect geode -- are hooked into the scene graph. Some
@@ -127,7 +135,7 @@ protected:
bool operator()(const Key& lhs, const Key& rhs) const;
};
};
typedef std::tr1::unordered_map<Key, osg::observer_ptr<Effect>,
typedef std::unordered_map<Key, osg::observer_ptr<Effect>,
boost::hash<Key>, Key::EqualTo> Cache;
Cache* getCache()
{
@@ -168,5 +176,33 @@ void mergePropertyTrees(SGPropertyNode* resultNode,
const SGPropertyNode* left,
const SGPropertyNode* right);
}
class UniformFactoryImpl {
public:
ref_ptr<Uniform> getUniform( Effect * effect,
const string & name,
Uniform::Type uniformType,
SGConstPropertyNode_ptr valProp,
const SGReaderWriterOptions* options );
void updateListeners( SGPropertyNode* propRoot );
void addListener(DeferredPropertyListener* listener);
void reset();
private:
// Default names for vector property components
static const char* vec3Names[];
static const char* vec4Names[];
SGMutex _mutex;
typedef boost::tuple<std::string, Uniform::Type, std::string, std::string> UniformCacheKey;
typedef boost::tuple<ref_ptr<Uniform>, SGPropertyChangeListener*> UniformCacheValue;
std::map<UniformCacheKey,ref_ptr<Uniform> > uniformCache;
typedef std::queue<DeferredPropertyListener*> DeferredListenerList;
DeferredListenerList deferredListenerList;
};
typedef Singleton<UniformFactoryImpl> UniformFactory;
}
#endif

View File

@@ -278,8 +278,10 @@ Effect* makeEffect(SGPropertyNode* prop,
void clearEffectCache()
{
SG_LOG(SG_INPUT, SG_DEBUG, "clearEffectCache called");
OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(effectMutex);
effectMap.clear();
UniformFactory::instance()->reset();
}
}

View File

@@ -708,67 +708,77 @@ protected:
*/
bool SGAnimation::setCenterAndAxisFromObject(osg::Node *rootNode, SGVec3d& center, SGVec3d &axis, simgear::SGTransientModelData &modelData) const
{
std::string axis_object_name = std::string();
bool can_warn = true;
if (_configNode->hasValue("axis/object-name"))
{
std::string axis_object_name = _configNode->getStringValue("axis/object-name");
if (!axis_object_name.empty())
axis_object_name = _configNode->getStringValue("axis/object-name");
}
else if (!_configNode->getNode("axis")) {
axis_object_name = _configNode->getStringValue("object-name") + std::string("-axis");
// for compatibility we will not warn if no axis object can be found when there was nothing
// specified - as the axis could just be the default at the origin
// so if there is a [objectname]-axis use it, otherwise fallback to the previous behaviour
can_warn = false;
}
if (!axis_object_name.empty())
{
/*
* First search the currently loaded cache map to see if this axis object has already been located.
* If we find it, we use it.
*/
const SGLineSegment<double> *axisSegment = modelData.getAxisDefinition(axis_object_name);
if (!axisSegment)
{
/*
* First search the currently loaded cache map to see if this axis object has already been located.
* If we find it, we use it.
*/
const SGLineSegment<double> *axisSegment = modelData.getAxisDefinition(axis_object_name);
if (!axisSegment)
* Find the object by name
*/
FindGroupVisitor axis_object_name_finder(axis_object_name);
rootNode->accept(axis_object_name_finder);
osg::Group *object_group = axis_object_name_finder.getGroup();
if (object_group)
{
/*
* Find the object by name
* we have found the object group (for the axis). This should be two vertices
* Now process this (with the line collector) to get the vertices.
* Once we have that we can then calculate the center and the affected axes.
*/
FindGroupVisitor axis_object_name_finder(axis_object_name);
rootNode->accept(axis_object_name_finder);
osg::Group *object_group = axis_object_name_finder.getGroup();
object_group->setNodeMask(0xffffffff);
LineCollector lineCollector;
object_group->accept(lineCollector);
std::vector<SGLineSegmentf> segs = lineCollector.getLineSegments();
if (object_group)
if (!segs.empty())
{
/*
* we have found the object group (for the axis). This should be two vertices
* Now process this (with the line collector) to get the vertices.
* Once we have that we can then calculate the center and the affected axes.
* Store the axis definition in the map; as once hidden it will not be possible
* to locate it again (and in any case it will be quicker to do it this way)
* This makes the axis/center static; there could be a use case for making this
* dynamic (and rebuilding the transforms), in which case this would need to
* do something different with the object; possibly storing a reference to the node
* so it can be extracted for dynamic processing.
*/
object_group->setNodeMask(0xffffffff);
LineCollector lineCollector;
object_group->accept(lineCollector);
std::vector<SGLineSegmentf> segs = lineCollector.getLineSegments();
if (!segs.empty())
{
/*
* Store the axis definition in the map; as once hidden it will not be possible
* to locate it again (and in any case it will be quicker to do it this way)
* This makes the axis/center static; there could be a use case for making this
* dynamic (and rebuilding the transforms), in which case this would need to
* do something different with the object; possibly storing a reference to the node
* so it can be extracted for dynamic processing.
*/
SGLineSegmentd segd(*(segs.begin()));
axisSegment = modelData.addAxisDefinition(axis_object_name,segd);
/*
* Hide the axis object. This also helps the modeller to know which axis animations are unassigned.
*/
object_group->setNodeMask(0);
}
else
SG_LOG(SG_INPUT, SG_ALERT, "Could find a valid line segment for animation: " << axis_object_name);
SGLineSegmentd segd(*(segs.begin()));
axisSegment = modelData.addAxisDefinition(axis_object_name, segd);
/*
* Hide the axis object. This also helps the modeller to know which axis animations are unassigned.
*/
object_group->setNodeMask(0);
}
else
SG_LOG(SG_INPUT, SG_ALERT, "Could not find at least one of the following objects for axis animation: " << axis_object_name);
}
if (axisSegment)
{
center = 0.5*(axisSegment->getStart() + axisSegment->getEnd());
axis = axisSegment->getEnd() - axisSegment->getStart();
return true;
SG_LOG(SG_INPUT, SG_ALERT, "Could find a valid line segment for animation: " << axis_object_name);
}
else if (can_warn)
SG_LOG(SG_INPUT, SG_ALERT, "Could not find at least one of the following objects for axis animation: " << axis_object_name);
}
if (axisSegment)
{
center = 0.5*(axisSegment->getStart() + axisSegment->getEnd());
axis = axisSegment->getEnd() - axisSegment->getStart();
return true;
}
}
return false;
@@ -2355,7 +2365,7 @@ SGTexTransformAnimation::createAnimationGroup(osg::Group& parent)
osg::Group* group = new osg::Group;
group->setName("texture transform group");
osg::StateSet* stateSet = group->getOrCreateStateSet();
stateSet->setDataVariance(osg::Object::DYNAMIC);
stateSet->setDataVariance(osg::Object::STATIC/*osg::Object::DYNAMIC*/);
osg::TexMat* texMat = new osg::TexMat;
UpdateCallback* updateCallback = new UpdateCallback(getCondition());
// interpret the configs ...

View File

@@ -149,7 +149,7 @@ Geometry* makeSharedTreeGeometry(int numQuads)
result->setVertexArray(v);
result->setTexCoordArray(0, t, Array::BIND_PER_VERTEX);
result->setComputeBoundingBoxCallback(new TreesBoundingBoxCallback);
result->setUseDisplayList(false);
//result->setUseDisplayList(false);
return result;
}

View File

@@ -55,7 +55,6 @@ class Effect;
// appropriate extensions are available.)
inline void SGConfigureDirectionalLights( bool use_point_sprites,
bool enhanced_lighting,
bool distance_attenuation ) {
static SGSceneFeatures* sceneFeatures = SGSceneFeatures::instance();
sceneFeatures->setEnablePointSpriteLights(use_point_sprites);

View File

@@ -69,7 +69,9 @@ SGBinding::read(const SGPropertyNode* node, SGPropertyNode* root)
}
_arg = const_cast<SGPropertyNode*>(node);
_root = const_cast<SGPropertyNode*>(root);
_setting = 0;
}
void
@@ -89,7 +91,7 @@ SGBinding::innerFire () const
SG_LOG(SG_INPUT, SG_WARN, "No command attached to binding:" << _command_name);
} else {
try {
if (!(*_command)(_arg)) {
if (!(*_command)(_arg, _root)) {
SG_LOG(SG_INPUT, SG_ALERT, "Failed to execute command "
<< _command_name);
}

View File

@@ -142,6 +142,7 @@ private:
mutable SGCommandMgr::Command* _command;
mutable SGPropertyNode_ptr _arg;
mutable SGPropertyNode_ptr _setting;
mutable SGPropertyNode_ptr _root;
};
typedef SGSharedPtr<SGBinding> SGBinding_ptr;

View File

@@ -75,7 +75,7 @@ SGCommandMgr::getCommandNames () const
}
bool
SGCommandMgr::execute (const std::string &name, const SGPropertyNode * arg) const
SGCommandMgr::execute (const std::string &name, const SGPropertyNode * arg, SGPropertyNode *root) const
{
Command* command = getCommand(name);
if (command == 0)
@@ -86,7 +86,7 @@ SGCommandMgr::execute (const std::string &name, const SGPropertyNode * arg) cons
try
{
return (*command)(arg);
return (*command)(arg, root);
}
catch(sg_exception& e)
{

View File

@@ -41,11 +41,11 @@ public:
{
public:
virtual ~Command() { }
virtual bool operator()(const SGPropertyNode * arg) = 0;
virtual bool operator()(const SGPropertyNode * arg, SGPropertyNode *root) = 0;
};
typedef bool (*command_t) (const SGPropertyNode * arg);
typedef bool (*command_t) (const SGPropertyNode * arg, SGPropertyNode *root);
private:
class FunctionCommand : public Command
@@ -54,7 +54,7 @@ private:
FunctionCommand( command_t fun )
: f_(fun) {}
virtual bool operator()(const SGPropertyNode * arg) { return (*f_)(arg); }
virtual bool operator()(const SGPropertyNode * arg, SGPropertyNode *root) { return (*f_)(arg, root); }
private:
command_t f_;
};
@@ -66,9 +66,9 @@ private:
MethodCommand( const ObjPtr& pObj, MemFn pMemFn ) :
pObj_(pObj), pMemFn_(pMemFn) {}
virtual bool operator()(const SGPropertyNode * arg)
virtual bool operator()(const SGPropertyNode * arg, SGPropertyNode *root)
{
return ((*pObj_).*pMemFn_)(arg);
return ((*pObj_).*pMemFn_)(arg,root);
}
private:
ObjPtr pObj_;
@@ -147,7 +147,7 @@ public:
* @return true if the command is present and executes successfully,
* false otherwise.
*/
virtual bool execute (const std::string &name, const SGPropertyNode * arg) const;
virtual bool execute (const std::string &name, const SGPropertyNode * arg, SGPropertyNode *root) const;
/**
* Remove a command registration

View File

@@ -49,7 +49,7 @@ class DummyThing
public:
DummyThing() : dummy_cmd_state(0) { }
bool someCommand(const SGPropertyNode* arg)
bool someCommand(const SGPropertyNode* arg, SGPropertyNode * )
{
dummy_cmd_state++;
return true;

View File

@@ -330,6 +330,11 @@ public:
*/
string_list member_names() const;
template<class T>
T* get_subsystem()
{
return dynamic_cast<T*>(get_subsystem(T::subsystemName()));
}
private:
class Member;

View File

@@ -1 +1 @@
2017.2.1
2017.3.1