Files
simgear/simgear/embedded_resources/EmbeddedResourceManager.cxx
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

226 lines
7.1 KiB
C++

// -*- 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()
{ }
string
EmbeddedResourceManager::Impl::getLocale() const
{
return selectedLocale;
}
string
EmbeddedResourceManager::Impl::selectLocale(const std::string& locale)
{
string previousLocale = std::move(selectedLocale);
selectedLocale = locale;
// 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);
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 + "'");
}
}
// ***************************************************************************
// * 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
{
// Failure would indicate that either no resource has been added, or
// selectLocale() hasn't been called. Remember that selectLocale() must be
// called after all resources have been added.
assert(!p->poolSearchList.empty());
// 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 this can be used to find a resource for
// any locale without any need to call selectLocale().
return p->lookupResourceInPools(virtualPath,
p->listOfResourcePoolsToSearch(locale));
}
} // of namespace simgear