1/*
2 * Copyright 2007, Haiku. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan A��mus <superstippi@gmx.de>
7 */
8
9#include "FontCache.h"
10
11#include <new>
12#include <stdio.h>
13#include <string.h>
14
15#include <Entry.h>
16#include <Path.h>
17
18#include "AutoLocker.h"
19
20
21using std::nothrow;
22
23
24FontCache
25FontCache::sDefaultInstance;
26
27// #pragma mark -
28
29// constructor
30FontCache::FontCache()
31	: MultiLocker("FontCache lock")
32	, fFontCacheEntries()
33{
34}
35
36// destructor
37FontCache::~FontCache()
38{
39	FontMap::Iterator iterator = fFontCacheEntries.GetIterator();
40	while (iterator.HasNext())
41		iterator.Next().value->ReleaseReference();
42}
43
44// Default
45/*static*/ FontCache*
46FontCache::Default()
47{
48	return &sDefaultInstance;
49}
50
51// FontCacheEntryFor
52FontCacheEntry*
53FontCache::FontCacheEntryFor(const ServerFont& font, bool forceVector)
54{
55	static const size_t signatureSize = 512;
56	char signature[signatureSize];
57	FontCacheEntry::GenerateSignature(signature, signatureSize, font,
58		forceVector);
59
60	AutoReadLocker readLocker(this);
61
62	FontCacheEntry* entry = fFontCacheEntries.Get(signature);
63
64	if (entry) {
65		// the entry was already there
66		entry->AcquireReference();
67//printf("FontCacheEntryFor(%ld): %p\n", font.GetFamilyAndStyle(), entry);
68		return entry;
69	}
70
71	readLocker.Unlock();
72
73	AutoWriteLocker locker(this);
74	if (!locker.IsLocked())
75		return NULL;
76
77	// prevent getting screwed by a race condition:
78	// when we released the readlock above, another thread might have
79	// gotten the writelock before we have, and might have already
80	// inserted a cache entry for this font. So we look again if there
81	// is an entry now, and only then create it if it's still not there,
82	// all while holding the writelock
83	entry = fFontCacheEntries.Get(signature);
84
85	if (!entry) {
86		// remove old entries, keep entries below certain count
87		_ConstrainEntryCount();
88		entry = new (nothrow) FontCacheEntry();
89		if (!entry || !entry->Init(font, forceVector)
90			|| fFontCacheEntries.Put(signature, entry) < B_OK) {
91			fprintf(stderr, "FontCache::FontCacheEntryFor() - "
92				"out of memory or no font file\n");
93			delete entry;
94			return NULL;
95		}
96	}
97//printf("FontCacheEntryFor(%ld): %p (insert)\n", font.GetFamilyAndStyle(), entry);
98
99	entry->AcquireReference();
100	return entry;
101}
102
103// Recycle
104void
105FontCache::Recycle(FontCacheEntry* entry)
106{
107//printf("Recycle(%p)\n", entry);
108	if (!entry)
109		return;
110	entry->UpdateUsage();
111	entry->ReleaseReference();
112}
113
114static const int32 kMaxEntryCount = 30;
115
116static inline double
117usage_index(uint64 useCount, bigtime_t age)
118{
119	return 100.0 * useCount / age;
120}
121
122// _ConstrainEntryCount
123void
124FontCache::_ConstrainEntryCount()
125{
126	// this function is only ever called with the WriteLock held
127	if (fFontCacheEntries.Size() < kMaxEntryCount)
128		return;
129//printf("FontCache::_ConstrainEntryCount()\n");
130
131	FontMap::Iterator iterator = fFontCacheEntries.GetIterator();
132
133	// NOTE: if kMaxEntryCount has a sane value, there has got to be
134	// some entries, so using the iterator like that should be ok
135	FontCacheEntry* leastUsedEntry = iterator.Next().value;
136	bigtime_t now = system_time();
137	bigtime_t age = now - leastUsedEntry->LastUsed();
138	uint64 useCount = leastUsedEntry->UsedCount();
139	double leastUsageIndex = usage_index(useCount, age);
140//printf("  leastUsageIndex: %f\n", leastUsageIndex);
141
142	while (iterator.HasNext()) {
143		FontCacheEntry* entry = iterator.Next().value;
144		age = now - entry->LastUsed();
145		useCount = entry->UsedCount();
146		double usageIndex = usage_index(useCount, age);
147//printf("  usageIndex: %f\n", usageIndex);
148		if (usageIndex < leastUsageIndex) {
149			leastUsedEntry = entry;
150			leastUsageIndex = usageIndex;
151		}
152	}
153
154	iterator = fFontCacheEntries.GetIterator();
155	while (iterator.HasNext()) {
156		if (iterator.Next().value == leastUsedEntry) {
157			fFontCacheEntries.Remove(iterator);
158			leastUsedEntry->ReleaseReference();
159			break;
160		}
161	}
162}
163