blob: a9a0d0cdabed1df54cdd3cafc0a7f49de1c339ec [file] [log] [blame]
/*
* Copyright (C) 2015 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 "resource_in_memory_cache.h"
#include <gapic/log.h>
#include <string.h>
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
namespace gapir {
std::unique_ptr<ResourceInMemoryCache> ResourceInMemoryCache::create(
std::unique_ptr<ResourceProvider> fallbackProvider, void* baseAddress) {
return std::unique_ptr<ResourceInMemoryCache>(
new ResourceInMemoryCache(std::move(fallbackProvider), baseAddress));
}
ResourceInMemoryCache::ResourceInMemoryCache(std::unique_ptr<ResourceProvider> fallbackProvider,
void* baseAddress) :
mFallbackProvider(std::move(fallbackProvider)),
mBaseAddress(static_cast<uint8_t*>(baseAddress)),
mSize(0) {
}
ResourceInMemoryCache::Entry::Entry(const ResourceId& id, size_t begin, size_t end) :
id(id), begin(begin), end(end) {
}
bool ResourceInMemoryCache::get(const ResourceId& id, const ServerConnection& gazer, void* target,
uint32_t size) {
const auto& iter = mCache.find(id);
if (iter != mCache.end()) {
// Requested resource is in the cache. Do a memory copy only
memcpy(target, mBaseAddress + iter->second, size);
return true;
} else {
// Requested resource not in the cache. Fetch it from the fall back provider
bool fallbackresult = mFallbackProvider->get(id, gazer, target, size);
if (!fallbackresult) {
// Fall back provider returned an error.
return false;
}
// Allocate memory for the resource in the cache and save it if the allocation was
// successful
uint8_t* ptr = allocateMemory(id, size);
if (ptr != nullptr) {
mCache.emplace(id, ptr - mBaseAddress);
memcpy(ptr, target, size);
}
return true;
}
}
bool ResourceInMemoryCache::getUncached(const ResourceId& id, const ServerConnection& gazer,
void* target, uint32_t size) {
return mFallbackProvider->getUncached(id, gazer, target, size);
}
bool ResourceInMemoryCache::prefetch(const ResourceList& resources,
const ServerConnection& gazer, void* buffer, uint32_t size) {
// Filter out the resources from the prefetch request which are already in cache before
// forwarding the request to the fall back provider
ResourceList missingResources;
std::copy_if(resources.begin(), resources.end(), std::back_inserter(missingResources),
[this](const std::pair<ResourceId, uint32_t>& r) {
return this->mCache.count(r.first) == 0;
});
return mFallbackProvider->prefetch(missingResources, gazer, buffer, size);
}
void ResourceInMemoryCache::updateSize(size_t newSize) {
// If the size will be decreased then remove the resources not fitting into the cache anymore
if (newSize <= mSize) {
auto it = mBlockList.end(); // Iterator pointing after the last block before each iteration
while (true) {
// If there is no more block in the cache then we have nothing left to remove
if (mBlockList.size() == 0) {
break;
}
--it; // Move to the next block from the end of the block list
// If a block (at least partially) lies outside the new memory range then remove it from
// the cache
if (it->end > newSize) {
mCache.erase(it->id);
it = mBlockList.erase(it);
} else {
break;
}
}
}
mSize = newSize;
}
uint8_t* ResourceInMemoryCache::allocateMemory(const std::string& id, size_t size) {
// If the requested size is greater then the cache size the the allocation is unsuccessful
if (size > mSize) {
return nullptr;
}
while (true) {
// If there is no block in the cache then the new block can be allocated at the base address
if (mBlockList.empty()) {
mBlockList.emplace_back(id, 0, size);
mLastBlock = mBlockList.begin();
mNextBlock = mBlockList.begin();
return mBaseAddress;
}
// Calculate the begin and the end offset of the currently empty (and active) memory range
size_t begin, end;
if (mNextBlock->begin > mLastBlock->end) {
// | occupied space | last block | free space | next block | occupied space | padding |
begin = mLastBlock->end;
end = mNextBlock->begin;
} else if (mNextBlock->begin > 0) {
// | free space | next block | occupied space | last block | padding |
begin = 0;
end = mNextBlock->begin;
} else {
// | last and next block | free space |
begin = mLastBlock->end;
end = mSize;
}
// If the requested size fits into the current free space then the allocation is successful
if (end - begin >= size) {
if (mNextBlock->begin == 0) {
// Next block is at the beginning of the cache. New blocks have to be inserted after
// last block what is the last element in the cache
mLastBlock = mBlockList.emplace(mBlockList.end(), id, begin, begin + size);
} else {
mLastBlock = mBlockList.emplace(mNextBlock, id, begin, begin + size);
}
return mBaseAddress + mLastBlock->begin;
} else {
// The size of the free space isn't enough. Remove the block pointed by the next block
// iterator and then try again (continue the iteration)
mCache.erase(mNextBlock->id);
mNextBlock = mBlockList.erase(mNextBlock);
if (mNextBlock == mBlockList.end()) {
mNextBlock = mBlockList.begin();
}
}
}
}
} // namespace gapir