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