1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles
3 /***************************************************************************
4 
5     drivenum.cpp
6 
7     Driver enumeration helpers.
8 
9 ***************************************************************************/
10 
11 #include "emu.h"
12 #include "drivenum.h"
13 #include "softlist_dev.h"
14 
15 #include <algorithm>
16 
17 #include <cctype>
18 
19 
20 
21 //**************************************************************************
22 //  DRIVER LIST
23 //**************************************************************************
24 
25 //-------------------------------------------------
26 //  driver_list - constructor
27 //-------------------------------------------------
28 
driver_list()29 driver_list::driver_list()
30 {
31 }
32 
33 
34 //-------------------------------------------------
35 //  find - find a driver by name
36 //-------------------------------------------------
37 
find(const char * name)38 int driver_list::find(const char *name)
39 {
40 	// if no name, bail
41 	if (!name)
42 		return -1;
43 
44 	// binary search to find it
45 	game_driver const *const *const begin = s_drivers_sorted;
46 	game_driver const *const *const end = begin + s_driver_count;
47 	auto const cmp = [] (game_driver const *driver, char const *name) { return core_stricmp(driver->name, name) < 0; };
48 	game_driver const *const *const result = std::lower_bound(begin, end, name, cmp);
49 	return ((result == end) || core_stricmp((*result)->name, name)) ? -1 : std::distance(begin, result);
50 }
51 
52 
53 //-------------------------------------------------
54 //  matches - true if we match, taking into
55 //  account wildcards in the wildstring
56 //-------------------------------------------------
57 
matches(const char * wildstring,const char * string)58 bool driver_list::matches(const char *wildstring, const char *string)
59 {
60 	// can only match internal drivers if the wildstring starts with an underscore
61 	if (string[0] == '_' && (wildstring == nullptr || wildstring[0] != '_'))
62 		return false;
63 
64 	// match everything else normally
65 	return (wildstring == nullptr || core_strwildcmp(wildstring, string) == 0);
66 }
67 
68 
69 
70 //**************************************************************************
71 //  DRIVER ENUMERATOR
72 //**************************************************************************
73 
74 //-------------------------------------------------
75 //  driver_enumerator - constructor
76 //-------------------------------------------------
77 
driver_enumerator(emu_options & options)78 driver_enumerator::driver_enumerator(emu_options &options)
79 	: m_current(-1)
80 	, m_filtered_count(0)
81 	, m_options(options)
82 	, m_included(s_driver_count)
83 	, m_config(CONFIG_CACHE_COUNT)
84 {
85 	include_all();
86 }
87 
88 
driver_enumerator(emu_options & options,const char * string)89 driver_enumerator::driver_enumerator(emu_options &options, const char *string)
90 	: driver_enumerator(options)
91 {
92 	filter(string);
93 }
94 
95 
driver_enumerator(emu_options & options,const game_driver & driver)96 driver_enumerator::driver_enumerator(emu_options &options, const game_driver &driver)
97 	: driver_enumerator(options)
98 {
99 	filter(driver);
100 }
101 
102 
103 //-------------------------------------------------
104 //  ~driver_enumerator - destructor
105 //-------------------------------------------------
106 
~driver_enumerator()107 driver_enumerator::~driver_enumerator()
108 {
109 	// configs are freed by the cache
110 }
111 
112 
113 //-------------------------------------------------
114 //  config - return a machine_config for the given
115 //  driver, allocating on demand if needed
116 //-------------------------------------------------
117 
config(std::size_t index,emu_options & options) const118 std::shared_ptr<machine_config> const &driver_enumerator::config(std::size_t index, emu_options &options) const
119 {
120 	assert(index < s_driver_count);
121 
122 	// if we don't have it cached, add it
123 	std::shared_ptr<machine_config> &config = m_config[index];
124 	if (!config)
125 		config = std::make_shared<machine_config>(*s_drivers_sorted[index], options);
126 
127 	return config;
128 }
129 
130 
131 //-------------------------------------------------
132 //  filter - filter the driver list against the
133 //  given string
134 //-------------------------------------------------
135 
filter(const char * filterstring)136 std::size_t driver_enumerator::filter(const char *filterstring)
137 {
138 	// reset the count
139 	exclude_all();
140 
141 	// match name against each driver in the list
142 	for (std::size_t index = 0; index < s_driver_count; index++)
143 		if (matches(filterstring, s_drivers_sorted[index]->name))
144 			include(index);
145 
146 	return m_filtered_count;
147 }
148 
149 
150 //-------------------------------------------------
151 //  filter - filter the driver list against the
152 //  given driver
153 //-------------------------------------------------
154 
filter(const game_driver & driver)155 std::size_t driver_enumerator::filter(const game_driver &driver)
156 {
157 	// reset the count
158 	exclude_all();
159 
160 	// match name against each driver in the list
161 	for (std::size_t index = 0; index < s_driver_count; index++)
162 		if (s_drivers_sorted[index] == &driver)
163 			include(index);
164 
165 	return m_filtered_count;
166 }
167 
168 
169 //-------------------------------------------------
170 //  include_all - include all non-internal drivers
171 //-------------------------------------------------
172 
include_all()173 void driver_enumerator::include_all()
174 {
175 	std::fill(m_included.begin(), m_included.end(), true);
176 	m_filtered_count = m_included.size();
177 
178 	// always exclude the empty driver
179 	exclude(find("___empty"));
180 }
181 
182 
183 //-------------------------------------------------
184 //  next - get the next driver matching the given
185 //  filter
186 //-------------------------------------------------
187 
next()188 bool driver_enumerator::next()
189 {
190 	release_current();
191 
192 	// always advance one
193 	// if we have a filter, scan forward to the next match
194 	for (m_current++; (m_current < s_driver_count) && !m_included[m_current]; m_current++) { }
195 
196 	// return true if we end up in range
197 	return (m_current >= 0) && (m_current < s_driver_count);
198 }
199 
200 
201 //-------------------------------------------------
202 //  next_excluded - get the next driver that is
203 //  not currently included in the list
204 //-------------------------------------------------
205 
next_excluded()206 bool driver_enumerator::next_excluded()
207 {
208 	release_current();
209 
210 	// always advance one
211 	// if we have a filter, scan forward to the next match
212 	for (m_current++; (m_current < s_driver_count) && m_included[m_current]; m_current++) { }
213 
214 	// return true if we end up in range
215 	return (m_current >= 0) && (m_current < s_driver_count);
216 }
217 
218 
219 //-------------------------------------------------
220 //  driver_sort_callback - compare two items in
221 //  an array of game_driver pointers
222 //-------------------------------------------------
223 
find_approximate_matches(std::string const & string,std::size_t count,int * results)224 void driver_enumerator::find_approximate_matches(std::string const &string, std::size_t count, int *results)
225 {
226 #undef rand
227 
228 	// if no name, pick random entries
229 	if (string.empty())
230 	{
231 		// seed the RNG first
232 		srand(osd_ticks());
233 
234 		// allocate a temporary list
235 		std::vector<int> templist(m_filtered_count);
236 		int arrayindex = 0;
237 		for (int index = 0; index < s_driver_count; index++)
238 			if (m_included[index])
239 				templist[arrayindex++] = index;
240 		assert(arrayindex == m_filtered_count);
241 
242 		// shuffle
243 		for (int shufnum = 0; shufnum < (4 * s_driver_count); shufnum++)
244 		{
245 			int item1 = rand() % m_filtered_count;
246 			int item2 = rand() % m_filtered_count;
247 			int temp = templist[item1];
248 			templist[item1] = templist[item2];
249 			templist[item2] = temp;
250 		}
251 
252 		// copy out the first few entries
253 		for (int matchnum = 0; matchnum < count; matchnum++)
254 			results[matchnum] = templist[matchnum % m_filtered_count];
255 	}
256 	else
257 	{
258 		// allocate memory to track the penalty value
259 		std::vector<std::pair<double, int> > penalty;
260 		penalty.reserve(count);
261 		std::u32string const search(ustr_from_utf8(normalize_unicode(string, unicode_normalization_form::D, true)));
262 		std::string composed;
263 		std::u32string candidate;
264 
265 		// scan the entire drivers array
266 		for (int index = 0; index < s_driver_count; index++)
267 		{
268 			// skip things that can't run
269 			if (m_included[index])
270 			{
271 				// cheat on the shortname as it's always lowercase ASCII
272 				game_driver const &drv(*s_drivers_sorted[index]);
273 				std::size_t const namelen(std::strlen(drv.name));
274 				candidate.resize(namelen);
275 				std::copy_n(drv.name, namelen, candidate.begin());
276 				double curpenalty(util::edit_distance(search, candidate));
277 
278 				// if it's not a perfect match, try the description
279 				if (curpenalty)
280 				{
281 					candidate = ustr_from_utf8(normalize_unicode(drv.type.fullname(), unicode_normalization_form::D, true));
282 					double p(util::edit_distance(search, candidate));
283 					if (p < curpenalty)
284 						curpenalty = p;
285 				}
286 
287 				// also check "<manufacturer> <description>"
288 				if (curpenalty)
289 				{
290 					composed.assign(drv.manufacturer);
291 					composed.append(1, ' ');
292 					composed.append(drv.type.fullname());
293 					candidate = ustr_from_utf8(normalize_unicode(composed, unicode_normalization_form::D, true));
294 					double p(util::edit_distance(search, candidate));
295 					if (p < curpenalty)
296 						curpenalty = p;
297 				}
298 
299 				// insert into the sorted table of matches
300 				auto const it(std::upper_bound(penalty.begin(), penalty.end(), std::make_pair(curpenalty, index)));
301 				if (penalty.end() != it)
302 				{
303 					if (penalty.size() >= count)
304 						penalty.resize(count - 1);
305 					penalty.emplace(it, curpenalty, index);
306 				}
307 				else if (penalty.size() < count)
308 				{
309 					penalty.emplace(it, curpenalty, index);
310 				}
311 			}
312 		}
313 
314 		// copy to output and pad with -1
315 		std::fill(
316 				std::transform(
317 					penalty.begin(),
318 					penalty.end(),
319 					results,
320 					[] (std::pair<double, int> const &x) { return x.second; }),
321 				results + count,
322 				-1);
323 	}
324 }
325 
326 
327 //-------------------------------------------------
328 //  release_current - release bulky memory
329 //  structures from the current entry because
330 //  we're done with it
331 //-------------------------------------------------
332 
release_current() const333 void driver_enumerator::release_current() const
334 {
335 	// skip if no current entry
336 	if ((m_current >= 0) && (m_current < s_driver_count))
337 	{
338 		// skip if we haven't cached a config
339 		auto const cached = m_config.find(m_current);
340 		if (cached != m_config.end())
341 		{
342 			// iterate over software lists in this entry and reset
343 			for (software_list_device &swlistdev : software_list_device_iterator(cached->second->root_device()))
344 				swlistdev.release();
345 		}
346 	}
347 }
348