1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3 
4 #include "galaxy/GalaxyCache.h"
5 
6 #include "Game.h"
7 #include "Pi.h"
8 #include "galaxy/Galaxy.h"
9 #include "galaxy/GalaxyGenerator.h"
10 #include "galaxy/Sector.h"
11 #include "galaxy/StarSystem.h"
12 #include "utils.h"
13 #include <utility>
14 
15 //#define DEBUG_CACHE
16 
17 //virtual
18 
19 template <typename T, typename CompareT>
~GalaxyObjectCache()20 GalaxyObjectCache<T, CompareT>::~GalaxyObjectCache()
21 {
22 	for (Slave *s : m_slaves)
23 		s->MasterDeleted();
24 	assert(m_attic.empty()); // otherwise the objects will deregister at a cache that no longer exists
25 }
26 
27 template <typename T, typename CompareT>
AddToCache(std::vector<RefCountedPtr<T>> & objects)28 void GalaxyObjectCache<T, CompareT>::AddToCache(std::vector<RefCountedPtr<T>> &objects)
29 {
30 	PROFILE_SCOPED()
31 	for (auto it = objects.begin(), itEnd = objects.end(); it != itEnd; ++it) {
32 		auto inserted = m_attic.insert(std::make_pair(it->Get()->GetPath(), it->Get()));
33 		if (!inserted.second) {
34 			it->Reset(inserted.first->second);
35 		} else {
36 			(*it)->SetCache(this);
37 		}
38 	}
39 }
40 
41 template <typename T, typename CompareT>
GetIfCached(const SystemPath & path)42 RefCountedPtr<T> GalaxyObjectCache<T, CompareT>::GetIfCached(const SystemPath &path)
43 {
44 	RefCountedPtr<T> s;
45 	typename AtticMap::iterator i = m_attic.find(path);
46 	if (i != m_attic.end()) {
47 		s.Reset(i->second);
48 	}
49 
50 	return s;
51 }
52 
53 template <typename T, typename CompareT>
GetCached(const SystemPath & path)54 RefCountedPtr<T> GalaxyObjectCache<T, CompareT>::GetCached(const SystemPath &path)
55 {
56 	RefCountedPtr<T> s = this->GetIfCached(path);
57 	if (!s) {
58 		++m_cacheMisses;
59 		s = m_galaxy->GetGenerator()->Generate<T, GalaxyObjectCache<T, CompareT>>(RefCountedPtr<Galaxy>(m_galaxy), path, this);
60 		m_attic.insert(std::make_pair(path, s.Get()));
61 	} else {
62 		++m_cacheHits;
63 	}
64 	return s;
65 }
66 
67 template <typename T, typename CompareT>
HasCached(const SystemPath & path) const68 bool GalaxyObjectCache<T, CompareT>::HasCached(const SystemPath &path) const
69 {
70 	PROFILE_SCOPED()
71 
72 	return (m_attic.find(path) != m_attic.end());
73 }
74 
75 template <typename T, typename CompareT>
RemoveFromAttic(const SystemPath & path)76 void GalaxyObjectCache<T, CompareT>::RemoveFromAttic(const SystemPath &path)
77 {
78 	m_attic.erase(path);
79 }
80 
81 template <typename T, typename CompareT>
ClearCache()82 void GalaxyObjectCache<T, CompareT>::ClearCache()
83 {
84 	for (auto it = m_slaves.begin(), itEnd = m_slaves.end(); it != itEnd; ++it)
85 		(*it)->ClearCache();
86 }
87 
88 template <typename T, typename CompareT>
OutputCacheStatistics(bool reset)89 void GalaxyObjectCache<T, CompareT>::OutputCacheStatistics(bool reset)
90 {
91 	Output("%s: misses: %llu, slave hits: %llu, master hits: %llu\n", CACHE_NAME.c_str(), m_cacheMisses, m_cacheHitsSlave, m_cacheHits);
92 	if (reset)
93 		m_cacheMisses = m_cacheHitsSlave = m_cacheHits = 0;
94 }
95 
96 template <typename T, typename CompareT>
NewSlaveCache()97 RefCountedPtr<typename GalaxyObjectCache<T, CompareT>::Slave> GalaxyObjectCache<T, CompareT>::NewSlaveCache()
98 {
99 	return RefCountedPtr<Slave>(new Slave(this, RefCountedPtr<Galaxy>(m_galaxy), Pi::GetAsyncJobQueue()));
100 }
101 
102 template <typename T, typename CompareT>
Slave(GalaxyObjectCache<T,CompareT> * master,RefCountedPtr<Galaxy> galaxy,JobQueue * jobQueue)103 GalaxyObjectCache<T, CompareT>::Slave::Slave(GalaxyObjectCache<T, CompareT> *master, RefCountedPtr<Galaxy> galaxy, JobQueue *jobQueue) :
104 	m_master(master),
105 	m_galaxy(galaxy),
106 	m_jobs(Pi::GetAsyncJobQueue())
107 {
108 	m_master->m_slaves.insert(this);
109 }
110 
111 template <typename T, typename CompareT>
MasterDeleted()112 void GalaxyObjectCache<T, CompareT>::Slave::MasterDeleted()
113 {
114 	m_master = nullptr;
115 }
116 
117 template <typename T, typename CompareT>
GetIfCached(const SystemPath & path)118 RefCountedPtr<T> GalaxyObjectCache<T, CompareT>::Slave::GetIfCached(const SystemPath &path)
119 {
120 	typename CacheMap::iterator i = m_cache.find(path);
121 	if (i != m_cache.end())
122 		return (*i).second;
123 	return RefCountedPtr<T>();
124 }
125 
126 template <typename T, typename CompareT>
GetCached(const SystemPath & path)127 RefCountedPtr<T> GalaxyObjectCache<T, CompareT>::Slave::GetCached(const SystemPath &path)
128 {
129 	PROFILE_SCOPED()
130 
131 	typename CacheMap::iterator i = m_cache.find(path);
132 	if (i != m_cache.end()) {
133 		if (m_master)
134 			++m_master->m_cacheHitsSlave;
135 		return (*i).second;
136 	}
137 
138 	if (m_master) {
139 		auto inserted = m_cache.insert(std::make_pair(path, m_master->GetCached(path)));
140 		return inserted.first->second;
141 	} else {
142 		return RefCountedPtr<T>();
143 	}
144 }
145 
146 template <typename T, typename CompareT>
Erase(const SystemPath & path)147 void GalaxyObjectCache<T, CompareT>::Slave::Erase(const SystemPath &path) { m_cache.erase(path); }
148 
149 template <typename T, typename CompareT>
Erase(const typename CacheMap::const_iterator & it)150 void GalaxyObjectCache<T, CompareT>::Slave::Erase(const typename CacheMap::const_iterator &it) { m_cache.erase(it); }
151 
152 template <typename T, typename CompareT>
ClearCache()153 void GalaxyObjectCache<T, CompareT>::Slave::ClearCache() { m_cache.clear(); }
154 
155 template <typename T, typename CompareT>
~Slave()156 GalaxyObjectCache<T, CompareT>::Slave::~Slave()
157 {
158 #ifdef DEBUG_CACHE
159 	unsigned unique = 0;
160 	for (auto it = m_cache.begin(); it != m_cache.end(); ++it)
161 		if (it->second->GetRefCount() == 1)
162 			unique++;
163 	Output("%s: Discarding slave cache with " SIZET_FMT " entries (%u to be removed)\n", CACHE_NAME.c_str(), m_cache.size(), unique);
164 #endif
165 	if (m_master)
166 		m_master->m_slaves.erase(this);
167 }
168 
169 template <typename T, typename CompareT>
AddToCache(std::vector<RefCountedPtr<T>> & objects)170 void GalaxyObjectCache<T, CompareT>::Slave::AddToCache(std::vector<RefCountedPtr<T>> &objects)
171 {
172 	if (m_master) {
173 		m_master->AddToCache(objects); // This modifies the vector to the sectors already in the master cache
174 		for (auto it = objects.begin(), itEnd = objects.end(); it != itEnd; ++it) {
175 			m_cache.insert(std::make_pair(it->Get()->GetPath(), *it));
176 		}
177 	}
178 }
179 
180 template <typename T, typename CompareT>
FillCache(const typename GalaxyObjectCache<T,CompareT>::PathVector & paths,typename GalaxyObjectCache<T,CompareT>::CacheFilledCallback callback)181 void GalaxyObjectCache<T, CompareT>::Slave::FillCache(const typename GalaxyObjectCache<T, CompareT>::PathVector &paths,
182 	typename GalaxyObjectCache<T, CompareT>::CacheFilledCallback callback)
183 {
184 	// allocate some space for what we're about to chunk up
185 	std::vector<std::unique_ptr<PathVector>> vec_paths;
186 	vec_paths.reserve(paths.size() / CACHE_JOB_SIZE + 1);
187 	std::unique_ptr<PathVector> current_paths;
188 #ifdef DEBUG_CACHE
189 	size_t alreadyCached = m_cache.size();
190 	unsigned masterCached = 0;
191 	unsigned toBeCreated = 0;
192 #endif
193 
194 	// chop the paths into groups of CACHE_JOB_SIZE
195 	for (auto it = paths.begin(), itEnd = paths.end(); it != itEnd; ++it) {
196 		RefCountedPtr<T> s = m_master->GetIfCached(*it);
197 		if (s) {
198 			m_cache[*it] = s;
199 #ifdef DEBUG_CACHE
200 			++masterCached;
201 #endif
202 		} else {
203 			if (!current_paths) {
204 				current_paths.reset(new PathVector);
205 				current_paths->reserve(CACHE_JOB_SIZE);
206 			}
207 			current_paths->push_back(*it);
208 			if (current_paths->size() >= CACHE_JOB_SIZE) {
209 				vec_paths.push_back(std::move(current_paths));
210 			}
211 #ifdef DEBUG_CACHE
212 			++toBeCreated;
213 #endif
214 		}
215 	}
216 
217 	// catch the last loop in case it's got some entries (could be less than the spread width)
218 	if (current_paths) {
219 		vec_paths.push_back(std::move(current_paths));
220 	}
221 
222 #ifdef DEBUG_CACHE
223 	Output("%s: FillCache: " SIZET_FMT " cached, %u in master cache, %u to be created, will use " SIZET_FMT " jobs\n", CACHE_NAME.c_str(),
224 		alreadyCached, masterCached, toBeCreated, vec_paths.size());
225 #endif
226 
227 	if (vec_paths.empty()) {
228 		if (callback)
229 			callback();
230 	} else {
231 		// now add the batched jobs
232 		for (auto it = vec_paths.begin(), itEnd = vec_paths.end(); it != itEnd; ++it)
233 			m_jobs.Order(new GalaxyObjectCache<T, CompareT>::CacheJob(std::move(*it), this, m_galaxy, callback));
234 	}
235 }
236 
237 template <typename T, typename CompareT>
CacheJob(std::unique_ptr<std::vector<SystemPath>> path,typename GalaxyObjectCache<T,CompareT>::Slave * slaveCache,RefCountedPtr<Galaxy> galaxy,typename GalaxyObjectCache<T,CompareT>::CacheFilledCallback callback)238 GalaxyObjectCache<T, CompareT>::CacheJob::CacheJob(std::unique_ptr<std::vector<SystemPath>> path,
239 	typename GalaxyObjectCache<T, CompareT>::Slave *slaveCache, RefCountedPtr<Galaxy> galaxy,
240 	typename GalaxyObjectCache<T, CompareT>::CacheFilledCallback callback) :
241 	Job(),
242 	m_paths(std::move(path)),
243 	m_slaveCache(slaveCache),
244 	m_galaxy(galaxy),
245 	m_galaxyGenerator(galaxy->GetGenerator()),
246 	m_callback(callback)
247 {
248 	m_objects.reserve(m_paths->size());
249 }
250 
251 //virtual
252 template <typename T, typename CompareT>
OnRun()253 void GalaxyObjectCache<T, CompareT>::CacheJob::OnRun() // RUNS IN ANOTHER THREAD!! MUST BE THREAD SAFE!
254 {
255 	PROFILE_SCOPED()
256 	for (auto it = m_paths->begin(), itEnd = m_paths->end(); it != itEnd; ++it)
257 		m_objects.push_back(m_galaxyGenerator->Generate<T, GalaxyObjectCache<T, CompareT>>(m_galaxy, *it, nullptr));
258 }
259 
260 //virtual
261 template <typename T, typename CompareT>
OnFinish()262 void GalaxyObjectCache<T, CompareT>::CacheJob::OnFinish() // runs in primary thread of the context
263 {
264 	PROFILE_SCOPED()
265 	m_slaveCache->AddToCache(m_objects);
266 	if (m_slaveCache->m_jobs.IsEmpty() && m_callback)
267 		m_callback();
268 }
269 
270 /****** SectorCache ******/
271 
272 template <>
273 const std::string GalaxyObjectCache<Sector, SystemPath::LessSectorOnly>::CACHE_NAME("SectorCache");
274 
275 template class GalaxyObjectCache<Sector, SystemPath::LessSectorOnly>;
276 
277 /****** StarSystemCache ******/
278 
279 template <>
Slave(GalaxyObjectCache<StarSystem,SystemPath::LessSystemOnly> * master,RefCountedPtr<Galaxy> galaxy,JobQueue * jobQueue)280 GalaxyObjectCache<StarSystem, SystemPath::LessSystemOnly>::Slave::Slave(GalaxyObjectCache<StarSystem, SystemPath::LessSystemOnly> *master, RefCountedPtr<Galaxy> galaxy, JobQueue *jobQueue) :
281 	m_master(master),
282 	m_galaxy(galaxy),
283 	m_jobs(Pi::GetSyncJobQueue())
284 {
285 	m_master->m_slaves.insert(this);
286 }
287 
288 template <>
289 const std::string GalaxyObjectCache<StarSystem, SystemPath::LessSystemOnly>::CACHE_NAME("StarSystemCache");
290 
291 template class GalaxyObjectCache<StarSystem, SystemPath::LessSystemOnly>;
292