blob: 4bfdb1205e199c04b53f11940a66e2da2f0ea4fe [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ConfigDescription.h"
#include "ResourceTable.h"
#include "split/TableSplitter.h"
#include <algorithm>
#include <map>
#include <set>
#include <unordered_map>
#include <vector>
namespace aapt {
using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>;
using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
static ConfigDescription copyWithoutDensity(const ConfigDescription& config) {
ConfigDescription withoutDensity = config;
withoutDensity.density = 0;
return withoutDensity;
}
/**
* Selects values that match exactly the constraints given.
*/
class SplitValueSelector {
public:
SplitValueSelector(const SplitConstraints& constraints) {
for (const ConfigDescription& config : constraints.configs) {
if (config.density == 0) {
mDensityIndependentConfigs.insert(config);
} else {
mDensityDependentConfigToDensityMap[copyWithoutDensity(config)] = config.density;
}
}
}
std::vector<ResourceConfigValue*> selectValues(const ConfigDensityGroups& densityGroups,
ConfigClaimedMap* claimedValues) {
std::vector<ResourceConfigValue*> selected;
// Select the regular values.
for (auto& entry : *claimedValues) {
// Check if the entry has a density.
ResourceConfigValue* configValue = entry.first;
if (configValue->config.density == 0 && !entry.second) {
// This is still available.
if (mDensityIndependentConfigs.find(configValue->config) !=
mDensityIndependentConfigs.end()) {
selected.push_back(configValue);
// Mark the entry as taken.
entry.second = true;
}
}
}
// Now examine the densities
for (auto& entry : densityGroups) {
// We do not care if the value is claimed, since density values can be
// in multiple splits.
const ConfigDescription& config = entry.first;
const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
auto densityValueIter = mDensityDependentConfigToDensityMap.find(config);
if (densityValueIter != mDensityDependentConfigToDensityMap.end()) {
// Select the best one!
ConfigDescription targetDensity = config;
targetDensity.density = densityValueIter->second;
ResourceConfigValue* bestValue = nullptr;
for (ResourceConfigValue* thisValue : relatedValues) {
if (!bestValue ||
thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
bestValue = thisValue;
}
// When we select one of these, they are all claimed such that the base
// doesn't include any anymore.
(*claimedValues)[thisValue] = true;
}
assert(bestValue);
selected.push_back(bestValue);
}
}
return selected;
}
private:
std::set<ConfigDescription> mDensityIndependentConfigs;
std::map<ConfigDescription, uint16_t> mDensityDependentConfigToDensityMap;
};
/**
* Marking non-preferred densities as claimed will make sure the base doesn't include them,
* leaving only the preferred density behind.
*/
static void markNonPreferredDensitiesAsClaimed(uint16_t preferredDensity,
const ConfigDensityGroups& densityGroups,
ConfigClaimedMap* configClaimedMap) {
for (auto& entry : densityGroups) {
const ConfigDescription& config = entry.first;
const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
ConfigDescription targetDensity = config;
targetDensity.density = preferredDensity;
ResourceConfigValue* bestValue = nullptr;
for (ResourceConfigValue* thisValue : relatedValues) {
if (!bestValue) {
bestValue = thisValue;
} else if (thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
// Claim the previous value so that it is not included in the base.
(*configClaimedMap)[bestValue] = true;
bestValue = thisValue;
} else {
// Claim this value so that it is not included in the base.
(*configClaimedMap)[thisValue] = true;
}
}
assert(bestValue);
}
}
bool TableSplitter::verifySplitConstraints(IAaptContext* context) {
bool error = false;
for (size_t i = 0; i < mSplitConstraints.size(); i++) {
for (size_t j = i + 1; j < mSplitConstraints.size(); j++) {
for (const ConfigDescription& config : mSplitConstraints[i].configs) {
if (mSplitConstraints[j].configs.find(config) !=
mSplitConstraints[j].configs.end()) {
context->getDiagnostics()->error(DiagMessage() << "config '" << config
<< "' appears in multiple splits, "
<< "target split ambiguous");
error = true;
}
}
}
}
return !error;
}
void TableSplitter::splitTable(ResourceTable* originalTable) {
const size_t splitCount = mSplitConstraints.size();
for (auto& pkg : originalTable->packages) {
// Initialize all packages for splits.
for (size_t idx = 0; idx < splitCount; idx++) {
ResourceTable* splitTable = mSplits[idx].get();
splitTable->createPackage(pkg->name, pkg->id);
}
for (auto& type : pkg->types) {
if (type->type == ResourceType::kMipmap) {
// Always keep mipmaps.
continue;
}
for (auto& entry : type->entries) {
if (mConfigFilter) {
// First eliminate any resource that we definitely don't want.
for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
if (!mConfigFilter->match(configValue->config)) {
// null out the entry. We will clean up and remove nulls at the end
// for performance reasons.
configValue.reset();
}
}
}
// Organize the values into two separate buckets. Those that are density-dependent
// and those that are density-independent.
// One density technically matches all density, it's just that some densities
// match better. So we need to be aware of the full set of densities to make this
// decision.
ConfigDensityGroups densityGroups;
ConfigClaimedMap configClaimedMap;
for (const std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
if (configValue) {
configClaimedMap[configValue.get()] = false;
if (configValue->config.density != 0) {
// Create a bucket for this density-dependent config.
densityGroups[copyWithoutDensity(configValue->config)]
.push_back(configValue.get());
}
}
}
// First we check all the splits. If it doesn't match one of the splits, we
// leave it in the base.
for (size_t idx = 0; idx < splitCount; idx++) {
const SplitConstraints& splitConstraint = mSplitConstraints[idx];
ResourceTable* splitTable = mSplits[idx].get();
// Select the values we want from this entry for this split.
SplitValueSelector selector(splitConstraint);
std::vector<ResourceConfigValue*> selectedValues =
selector.selectValues(densityGroups, &configClaimedMap);
// No need to do any work if we selected nothing.
if (!selectedValues.empty()) {
// Create the same resource structure in the split. We do this lazily
// because we might not have actual values for each type/entry.
ResourceTablePackage* splitPkg = splitTable->findPackage(pkg->name);
ResourceTableType* splitType = splitPkg->findOrCreateType(type->type);
if (!splitType->id) {
splitType->id = type->id;
splitType->symbolStatus = type->symbolStatus;
}
ResourceEntry* splitEntry = splitType->findOrCreateEntry(entry->name);
if (!splitEntry->id) {
splitEntry->id = entry->id;
splitEntry->symbolStatus = entry->symbolStatus;
}
// Copy the selected values into the new Split Entry.
for (ResourceConfigValue* configValue : selectedValues) {
ResourceConfigValue* newConfigValue = splitEntry->findOrCreateValue(
configValue->config, configValue->product);
newConfigValue->value = std::unique_ptr<Value>(
configValue->value->clone(&splitTable->stringPool));
}
}
}
if (mPreferredDensity) {
markNonPreferredDensitiesAsClaimed(mPreferredDensity.value(),
densityGroups,
&configClaimedMap);
}
// All splits are handled, now check to see what wasn't claimed and remove
// whatever exists in other splits.
for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
if (configValue && configClaimedMap[configValue.get()]) {
// Claimed, remove from base.
configValue.reset();
}
}
// Now erase all nullptrs.
entry->values.erase(
std::remove(entry->values.begin(), entry->values.end(), nullptr),
entry->values.end());
}
}
}
}
} // namespace aapt