⛏️ index : haiku.git

/*
 * Copyright 2007, Haiku. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Stephan Aßmus <superstippi@gmx.de>
 */

#include "FontCache.h"

#include <new>
#include <stdio.h>
#include <string.h>

#include <Entry.h>
#include <Path.h>

#include "AutoLocker.h"


using std::nothrow;


FontCache
FontCache::sDefaultInstance;

// #pragma mark -

// constructor
FontCache::FontCache()
	: MultiLocker("FontCache lock")
	, fFontCacheEntries()
{
}

// destructor
FontCache::~FontCache()
{
}

// Default
/*static*/ FontCache*
FontCache::Default()
{
	return &sDefaultInstance;
}

// FontCacheEntryFor
FontCacheEntry*
FontCache::FontCacheEntryFor(const ServerFont& font, bool forceVector)
{
	static const size_t signatureSize = 512;
	char signature[signatureSize];
	FontCacheEntry::GenerateSignature(signature, signatureSize, font,
		forceVector);

	AutoReadLocker readLocker(this);

	BReference<FontCacheEntry> entry = fFontCacheEntries.Get(signature);

	if (entry) {
		// the entry was already there
//printf("FontCacheEntryFor(%ld): %p\n", font.GetFamilyAndStyle(), entry);
		return entry.Detach();
	}

	readLocker.Unlock();

	AutoWriteLocker locker(this);
	if (!locker.IsLocked())
		return NULL;

	// prevent getting screwed by a race condition:
	// when we released the readlock above, another thread might have
	// gotten the writelock before we have, and might have already
	// inserted a cache entry for this font. So we look again if there
	// is an entry now, and only then create it if it's still not there,
	// all while holding the writelock
	entry = fFontCacheEntries.Get(signature);

	if (!entry) {
		// remove old entries, keep entries below certain count
		_ConstrainEntryCount();
		entry.SetTo(new (nothrow) FontCacheEntry(), true);
		if (!entry || !entry->Init(font, forceVector)
			|| fFontCacheEntries.Put(signature, entry) < B_OK) {
			fprintf(stderr, "FontCache::FontCacheEntryFor() - "
				"out of memory or no font file\n");
			return NULL;
		}
	}
//printf("FontCacheEntryFor(%ld): %p (insert)\n", font.GetFamilyAndStyle(), entry);

	return entry.Detach();
}

// Recycle
void
FontCache::Recycle(FontCacheEntry* entry)
{
//printf("Recycle(%p)\n", entry);
	if (!entry)
		return;
	entry->UpdateUsage();
	entry->ReleaseReference();
}

static const int32 kMaxEntryCount = 30;

static inline double
usage_index(uint64 useCount, bigtime_t age)
{
	return 100.0 * useCount / age;
}

// _ConstrainEntryCount
void
FontCache::_ConstrainEntryCount()
{
	// this function is only ever called with the WriteLock held
	if (fFontCacheEntries.Size() < kMaxEntryCount)
		return;
//printf("FontCache::_ConstrainEntryCount()\n");

	FontMap::Iterator iterator = fFontCacheEntries.GetIterator();

	// NOTE: if kMaxEntryCount has a sane value, there has got to be
	// some entries, so using the iterator like that should be ok
	FontCacheEntry* leastUsedEntry = iterator.Next().value;
	bigtime_t now = system_time();
	bigtime_t age = now - leastUsedEntry->LastUsed();
	uint64 useCount = leastUsedEntry->UsedCount();
	double leastUsageIndex = usage_index(useCount, age);
//printf("  leastUsageIndex: %f\n", leastUsageIndex);

	while (iterator.HasNext()) {
		FontCacheEntry* entry = iterator.Next().value;
		age = now - entry->LastUsed();
		useCount = entry->UsedCount();
		double usageIndex = usage_index(useCount, age);
//printf("  usageIndex: %f\n", usageIndex);
		if (usageIndex < leastUsageIndex) {
			leastUsedEntry = entry;
			leastUsageIndex = usageIndex;
		}
	}

	iterator = fFontCacheEntries.GetIterator();
	while (iterator.HasNext()) {
		if (iterator.Next().value.Get() == leastUsedEntry) {
			fFontCacheEntries.Remove(iterator);
			break;
		}
	}
}