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)
54{
55	static const size_t signatureSize = 512;
56	char signature[signatureSize];
57	FontCacheEntry::GenerateSignature(signature, signatureSize, font);
58
59	AutoReadLocker readLocker(this);
60
61	FontCacheEntry* entry = fFontCacheEntries.Get(signature);
62
63	if (entry) {
64		// the entry was already there
65		entry->AcquireReference();
66//printf("FontCacheEntryFor(%ld): %p\n", font.GetFamilyAndStyle(), entry);
67		return entry;
68	}
69
70	readLocker.Unlock();
71
72	AutoWriteLocker locker(this);
73	if (!locker.IsLocked())
74		return NULL;
75
76	// prevent getting screwed by a race condition:
77	// when we released the readlock above, another thread might have
78	// gotten the writelock before we have, and might have already
79	// inserted a cache entry for this font. So we look again if there
80	// is an entry now, and only then create it if it's still not there,
81	// all while holding the writelock
82	entry = fFontCacheEntries.Get(signature);
83
84	if (!entry) {
85		// remove old entries, keep entries below certain count
86		_ConstrainEntryCount();
87		entry = new (nothrow) FontCacheEntry();
88		if (!entry || !entry->Init(font)
89			|| fFontCacheEntries.Put(signature, entry) < B_OK) {
90			fprintf(stderr, "FontCache::FontCacheEntryFor() - "
91				"out of memory or no font file\n");
92			delete entry;
93			return NULL;
94		}
95	}
96//printf("FontCacheEntryFor(%ld): %p (insert)\n", font.GetFamilyAndStyle(), entry);
97
98	entry->AcquireReference();
99	return entry;
100}
101
102// Recycle
103void
104FontCache::Recycle(FontCacheEntry* entry)
105{
106//printf("Recycle(%p)\n", entry);
107	if (!entry)
108		return;
109	entry->UpdateUsage();
110	entry->ReleaseReference();
111}
112
113static const int32 kMaxEntryCount = 30;
114
115static inline double
116usage_index(uint64 useCount, bigtime_t age)
117{
118	return 100.0 * useCount / age;
119}
120
121// _ConstrainEntryCount
122void
123FontCache::_ConstrainEntryCount()
124{
125	// this function is only ever called with the WriteLock held
126	if (fFontCacheEntries.Size() < kMaxEntryCount)
127		return;
128//printf("FontCache::_ConstrainEntryCount()\n");
129
130	FontMap::Iterator iterator = fFontCacheEntries.GetIterator();
131
132	// NOTE: if kMaxEntryCount has a sane value, there has got to be
133	// some entries, so using the iterator like that should be ok
134	FontCacheEntry* leastUsedEntry = iterator.Next().value;
135	bigtime_t now = system_time();
136	bigtime_t age = now - leastUsedEntry->LastUsed();
137	uint64 useCount = leastUsedEntry->UsedCount();
138	double leastUsageIndex = usage_index(useCount, age);
139//printf("  leastUsageIndex: %f\n", leastUsageIndex);
140
141	while (iterator.HasNext()) {
142		FontCacheEntry* entry = iterator.Next().value;
143		age = now - entry->LastUsed();
144		useCount = entry->UsedCount();
145		double usageIndex = usage_index(useCount, age);
146//printf("  usageIndex: %f\n", usageIndex);
147		if (usageIndex < leastUsageIndex) {
148			leastUsedEntry = entry;
149			leastUsageIndex = usageIndex;
150		}
151	}
152
153	iterator = fFontCacheEntries.GetIterator();
154	while (iterator.HasNext()) {
155		if (iterator.Next().value == leastUsedEntry) {
156			iterator.Remove();
157			leastUsedEntry->ReleaseReference();
158			break;
159		}
160	}
161}
162