openMSX
MSXDevice.cc
Go to the documentation of this file.
1 #include "MSXDevice.hh"
2 #include "XMLElement.hh"
3 #include "MSXMotherBoard.hh"
4 #include "HardwareConfig.hh"
6 #include "MSXCPUInterface.hh"
7 #include "MSXCPU.hh"
8 #include "CacheLine.hh"
9 #include "TclObject.hh"
10 #include "Math.hh"
11 #include "MSXException.hh"
12 #include "one_of.hh"
13 #include "ranges.hh"
14 #include "serialize.hh"
15 #include "stl.hh"
16 #include "unreachable.hh"
17 #include "view.hh"
18 #include "xrange.hh"
19 #include <cassert>
20 #include <cstring>
21 #include <iterator> // for back_inserter
22 
23 using std::string;
24 
25 namespace openmsx {
26 
27 MSXDevice::MSXDevice(const DeviceConfig& config, const string& name)
28  : deviceConfig(config)
29 {
30  initName(name);
31 }
32 
34  : deviceConfig(config)
35 {
36  initName(getDeviceConfig().getAttribute("id"));
37 }
38 
39 void MSXDevice::initName(const string& name)
40 {
41  deviceName = name;
42  if (getMotherBoard().findDevice(deviceName)) {
43  unsigned n = 0;
44  do {
45  deviceName = strCat(name, " (", ++n, ')');
46  } while (getMotherBoard().findDevice(deviceName));
47  }
48 }
49 
51 {
52  staticInit();
53 
54  lockDevices();
55  registerSlots();
56  registerPorts();
57 }
58 
60 {
61  unregisterPorts();
62  unregisterSlots();
63  unlockDevices();
64  assert(referencedBy.empty());
65 }
66 
67 void MSXDevice::staticInit()
68 {
69  static bool alreadyInit = false;
70  if (alreadyInit) return;
71  alreadyInit = true;
72 
73  memset(unmappedRead, 0xFF, sizeof(unmappedRead));
74 }
75 
77 {
79 }
80 
81 void MSXDevice::testRemove(Devices removed) const
82 {
83  auto all = referencedBy;
84  ranges::sort(all);
85  ranges::sort(removed);
86  Devices rest;
87  ranges::set_difference(all, removed, back_inserter(rest));
88  if (!rest.empty()) {
89  string msg = "Still in use by";
90  for (auto& d : rest) {
91  strAppend(msg, ' ', d->getName());
92  }
93  throw MSXException(std::move(msg));
94  }
95 }
96 
97 void MSXDevice::lockDevices()
98 {
99  // This code can only handle backward references: the thing that is
100  // referenced must already be instantiated, we don't try to change the
101  // instantiation order. For the moment this is good enough (only ADVRAM
102  // (an extension) uses it to refer to the VDP (inside a machine)). If
103  // needed we can implement something more sophisticated later without
104  // changing the format of the config files.
105  for (auto& c : getDeviceConfig().getChildren("device")) {
106  const auto& name = c->getAttribute("idref");
107  auto* dev = getMotherBoard().findDevice(name);
108  if (!dev) {
109  throw MSXException(
110  "Unsatisfied dependency: '", getName(),
111  "' depends on unavailable device '",
112  name, "'.");
113  }
114  references.push_back(dev);
115  dev->referencedBy.push_back(this);
116  }
117 }
118 
119 void MSXDevice::unlockDevices()
120 {
121  for (auto& r : references) {
122  move_pop_back(r->referencedBy, rfind_unguarded(r->referencedBy, this));
123  }
124 }
125 
127 {
128  // init() must already be called
129  return references;
130 }
131 
132 EmuTime::param MSXDevice::getCurrentTime() const
133 {
134  return getMotherBoard().getCurrentTime();
135 }
137 {
138  return getMotherBoard().getCPU();
139 }
141 {
142  return getMotherBoard().getCPUInterface();
143 }
145 {
146  return getMotherBoard().getScheduler();
147 }
149 {
150  return getMotherBoard().getMSXCliComm();
151 }
153 {
154  return getMotherBoard().getReactor();
155 }
157 {
159 }
161 {
162  return getMotherBoard().getLedStatus();
163 }
165 {
167 }
168 
169 void MSXDevice::registerSlots()
170 {
171  MemRegions tmpMemRegions;
172  unsigned align = getBaseSizeAlignment();
173  assert(Math::ispow2(align));
174  for (auto& m : getDeviceConfig().getChildren("mem")) {
175  unsigned base = m->getAttributeAsInt("base");
176  unsigned size = m->getAttributeAsInt("size");
177  if ((base >= 0x10000) || (size > 0x10000) || ((base + size) > 0x10000)) {
178  throw MSXException(
179  "Invalid memory specification for device ",
180  getName(), " should be in range "
181  "[0x0000,0x10000).");
182  }
183  if (((base & (align - 1)) || (size & (align - 1))) &&
184  !allowUnaligned()) {
185  throw MSXException(
186  "invalid memory specification for device ",
187  getName(), " should be aligned on at least 0x",
188  hex_string<4>(align), '.');
189  }
190  tmpMemRegions.emplace_back(base, size);
191  }
192  if (tmpMemRegions.empty()) {
193  return;
194  }
195 
196  // find primary and secondary slot specification
197  auto& slotManager = getMotherBoard().getSlotManager();
198  auto* primaryConfig = getDeviceConfig2().getPrimary();
199  auto* secondaryConfig = getDeviceConfig2().getSecondary();
200  if (primaryConfig) {
201  ps = slotManager.getSlotNum(primaryConfig->getAttribute("slot"));
202  } else {
203  throw MSXException("Invalid memory specification");
204  }
205  if (secondaryConfig) {
206  const auto& ss_str = secondaryConfig->getAttribute("slot");
207  ss = slotManager.getSlotNum(ss_str);
208  if ((-16 <= ss) && (ss <= -1) && (ss != ps)) {
209  throw MSXException(
210  "Invalid secondary slot specification: \"",
211  ss_str, "\".");
212  }
213  } else {
214  ss = 0;
215  }
216 
217  // This is only for backwards compatibility: in the past we added extra
218  // attributes "primary_slot" and "secondary_slot" (in each MSXDevice
219  // config) instead of changing the 'any' value of the slot attribute of
220  // the (possibly shared) <primary> and <secondary> tags. When loading
221  // an old savestate these tags can still occur, so keep this code. Also
222  // remove these attributes to convert to the new format.
223  const auto& config = getDeviceConfig();
224  if (config.hasAttribute("primary_slot")) {
225  auto& mutableConfig = const_cast<XMLElement&>(config);
226  const auto& primSlot = config.getAttribute("primary_slot");
227  ps = slotManager.getSlotNum(primSlot);
228  mutableConfig.removeAttribute("primary_slot");
229  if (config.hasAttribute("secondary_slot")) {
230  const auto& secondSlot = config.getAttribute("secondary_slot");
231  ss = slotManager.getSlotNum(secondSlot);
232  mutableConfig.removeAttribute("secondary_slot");
233  }
234  }
235 
236  // decode special values for 'ss'
237  if ((-128 <= ss) && (ss < 0)) {
238  if ((0 <= ps) && (ps < 4) &&
239  getCPUInterface().isExpanded(ps)) {
240  ss += 128;
241  } else {
242  ss = 0;
243  }
244  }
245 
246  // decode special values for 'ps'
247  if (ps == -256) {
248  slotManager.getAnyFreeSlot(ps, ss);
249  } else if (ps < 0) {
250  // specified slot by name (carta, cartb, ...)
251  slotManager.getSpecificSlot(-ps - 1, ps, ss);
252  } else {
253  // numerical specified slot (0, 1, 2, 3)
254  }
255  assert((0 <= ps) && (ps <= 3));
256 
257  if (!getCPUInterface().isExpanded(ps)) {
258  ss = -1;
259  }
260 
261  // Store actual slot in config. This has two purposes:
262  // - Make sure that devices that are grouped under the same
263  // <primary>/<secondary> tags actually use the same slot. (This
264  // matters when the value of some of the slot attributes is "any").
265  // - Fix the slot number so that it remains the same after a
266  // savestate/loadstate.
267  assert(primaryConfig);
268  primaryConfig->setAttribute("slot", strCat(ps));
269  if (secondaryConfig) {
270  string slot = (ss == -1) ? "X" : strCat(ss);
271  secondaryConfig->setAttribute("slot", std::move(slot));
272  } else {
273  if (ss != -1) {
274  throw MSXException(
275  "Missing <secondary> tag for device", getName());
276  }
277  }
278 
279  int logicalSS = (ss == -1) ? 0 : ss;
280  for (auto& r : tmpMemRegions) {
282  *this, ps, logicalSS, r.first, r.second);
283  memRegions.push_back(r);
284  }
285 
286  // Mark the slot as 'in-use' so that future searches for free external
287  // slots don't return this slot anymore. If the slot was not an
288  // external slot, this call has no effect. Multiple MSXDevices from the
289  // same extension (the same HardwareConfig) can all allocate the same
290  // slot (later they should also all free this slot).
291  slotManager.allocateSlot(ps, ss, getHardwareConfig());
292 }
293 
294 void MSXDevice::unregisterSlots()
295 {
296  if (memRegions.empty()) return;
297 
298  int logicalSS = (ss == -1) ? 0 : ss;
299  for (const auto& [base, size] : memRegions) {
301  *this, ps, logicalSS, base, size);
302  }
303 
304  // See comments above about allocateSlot() for more details:
305  // - has no effect for non-external slots
306  // - can be called multiple times for the same slot
308 }
309 
310 void MSXDevice::getVisibleMemRegion(unsigned& base, unsigned& size) const
311 {
312  // init() must already be called
313  if (memRegions.empty()) {
314  base = 0;
315  size = 0;
316  return;
317  }
318 
319  auto lowest = min_value(view::transform(memRegions,
320  [](auto& r) { return r.first; }));
321  auto highest = max_value(view::transform(memRegions,
322  [](auto& r) { return r.first + r.second; }));
323  assert(lowest <= highest);
324  base = lowest;
325  size = highest - lowest;
326 }
327 
328 void MSXDevice::registerPorts()
329 {
330  for (auto& i : getDeviceConfig().getChildren("io")) {
331  unsigned base = i->getAttributeAsInt("base");
332  unsigned num = i->getAttributeAsInt("num");
333  const auto& type = i->getAttribute("type", "IO");
334  if (((base + num) > 256) || (num == 0) ||
335  (type != one_of("I", "O", "IO"))) {
336  throw MSXException("Invalid IO port specification");
337  }
338  for (auto port : xrange(base, base + num)) {
339  if (type == one_of("I", "IO")) {
340  getCPUInterface().register_IO_In(port, this);
341  inPorts.push_back(port);
342  }
343  if (type == one_of("O", "IO")) {
344  getCPUInterface().register_IO_Out(port, this);
345  outPorts.push_back(port);
346  }
347  }
348  }
349 }
350 
351 void MSXDevice::unregisterPorts()
352 {
353  for (auto& p : inPorts) {
355  }
356  for (auto& p : outPorts) {
358  }
359 }
360 
361 
362 void MSXDevice::reset(EmuTime::param /*time*/)
363 {
364  // nothing
365 }
366 
368 {
369  return 0xFF;
370 }
371 
372 void MSXDevice::powerDown(EmuTime::param /*time*/)
373 {
374  // nothing
375 }
376 
377 void MSXDevice::powerUp(EmuTime::param time)
378 {
379  reset(time);
380 }
381 
382 string MSXDevice::getName() const
383 {
384  return deviceName;
385 }
386 
388 {
389  result.addListElement(getName());
390 }
391 
393 {
394  result.addDictKeyValue("type", getDeviceConfig().getName());
395  getExtraDeviceInfo(result);
396 }
397 
399 {
400  // nothing
401 }
402 
404 {
405  return CacheLine::SIZE;
406 }
407 
408 
409 byte MSXDevice::readIO(word /*port*/, EmuTime::param /*time*/)
410 {
411  // read from unmapped IO
412  return 0xFF;
413 }
414 
415 void MSXDevice::writeIO(word /*port*/, byte /*value*/, EmuTime::param /*time*/)
416 {
417  // write to unmapped IO, do nothing
418 }
419 
420 byte MSXDevice::peekIO(word /*port*/, EmuTime::param /*time*/) const
421 {
422  return 0xFF;
423 }
424 
425 
426 byte MSXDevice::readMem(word /*address*/, EmuTime::param /*time*/)
427 {
428  // read from unmapped memory
429  return 0xFF;
430 }
431 
432 const byte* MSXDevice::getReadCacheLine(word /*start*/) const
433 {
434  return nullptr; // uncacheable
435 }
436 
437 void MSXDevice::writeMem(word /*address*/, byte /*value*/,
438  EmuTime::param /*time*/)
439 {
440  // write to unmapped memory, do nothing
441 }
442 
443 byte MSXDevice::peekMem(word address, EmuTime::param /*time*/) const
444 {
445  word base = address & CacheLine::HIGH;
446  if (const byte* cache = getReadCacheLine(base)) {
447  word offset = address & CacheLine::LOW;
448  return cache[offset];
449  } else {
450  // peek not supported for this device
451  return 0xFF;
452  }
453 }
454 
455 void MSXDevice::globalWrite(word /*address*/, byte /*value*/,
456  EmuTime::param /*time*/)
457 {
458  UNREACHABLE;
459 }
460 
461 void MSXDevice::globalRead(word /*address*/, EmuTime::param /*time*/)
462 {
463  UNREACHABLE;
464 }
465 
466 byte* MSXDevice::getWriteCacheLine(word /*start*/) const
467 {
468  return nullptr; // uncacheable
469 }
470 
471 
472 // calls 'action(<start2>, <size2>, args..., ps, ss)'
473 // with 'start', 'size' clipped to each of the ranges in 'memRegions'
474 template<typename Action, typename... Args>
475 void MSXDevice::clip(unsigned start, unsigned size, Action action, Args... args)
476 {
477  int ss2 = (ss != -1) ? ss : 0;
478  unsigned end = start + size;
479  for (auto [base, fullBsize] : memRegions) {
480  // split on 16kB boundaries
481  while (fullBsize > 0) {
482  unsigned bsize = std::min(fullBsize, ((base + 0x4000) & ~0x3fff) - base);
483 
484  unsigned baseEnd = base + bsize;
485  // intersect [start, end) with [base, baseEnd) -> [clipStart, clipEnd)
486  unsigned clipStart = std::max(start, base);
487  unsigned clipEnd = std::min(end, baseEnd);
488  if (clipStart < clipEnd) { // non-empty
489  unsigned clipSize = clipEnd - clipStart;
490  action(clipStart, clipSize, args..., ps, ss2);
491  }
492 
493  base += bsize;
494  fullBsize -= bsize;
495  }
496  }
497 }
498 
499 void MSXDevice::invalidateDeviceRWCache(unsigned start, unsigned size)
500 {
501  clip(start, size, [&](auto... args) { getCPUInterface().invalidateRWCache(args...); });
502 }
503 void MSXDevice::invalidateDeviceRCache(unsigned start, unsigned size)
504 {
505  clip(start, size, [&](auto... args) { getCPUInterface().invalidateRCache(args...); });
506 }
507 void MSXDevice::invalidateDeviceWCache(unsigned start, unsigned size)
508 {
509  clip(start, size, [&](auto... args) { getCPUInterface().invalidateWCache(args...); });
510 }
511 
512 void MSXDevice::fillDeviceRWCache(unsigned start, unsigned size, byte* rwData)
513 {
514  fillDeviceRWCache(start, size, rwData, rwData);
515 }
516 void MSXDevice::fillDeviceRWCache(unsigned start, unsigned size, const byte* rData, byte* wData)
517 {
518  assert(!allowUnaligned());
519  clip(start, size, [&](auto... args) { getCPUInterface().fillRWCache(args...); }, rData, wData);
520 }
521 void MSXDevice::fillDeviceRCache(unsigned start, unsigned size, const byte* rData)
522 {
523  assert(!allowUnaligned());
524  clip(start, size, [&](auto... args) { getCPUInterface().fillRCache(args...); }, rData);
525 }
526 void MSXDevice::fillDeviceWCache(unsigned start, unsigned size, byte* wData)
527 {
528  assert(!allowUnaligned());
529  clip(start, size, [&](auto... args) { getCPUInterface().fillWCache(args...); }, wData);
530 }
531 
532 template<typename Archive>
533 void MSXDevice::serialize(Archive& ar, unsigned /*version*/)
534 {
535  // When this method is called, the method init() has already been
536  // called (thus also registerSlots() and registerPorts()).
537  ar.serialize("name", deviceName);
538 }
540 
541 } // namespace openmsx
Definition: one_of.hh:7
void freeSlot(int ps, int ss, const HardwareConfig &hwConfig)
XMLElement * getSecondary() const
Definition: DeviceConfig.hh:55
XMLElement * getPrimary() const
Definition: DeviceConfig.hh:51
MSXMotherBoard & getMotherBoard() const
void register_IO_Out(byte port, MSXDevice *device)
Devices can register their Out ports.
void invalidateRWCache(word start, unsigned size, int ps, int ss)
void register_IO_In(byte port, MSXDevice *device)
Devices can register their In ports.
void fillWCache(unsigned start, unsigned size, byte *wData, int ps, int ss)
void unregister_IO_In(byte port, MSXDevice *device)
void invalidateRCache(word start, unsigned size, int ps, int ss)
void fillRWCache(unsigned start, unsigned size, const byte *rData, byte *wData, int ps, int ss)
void registerMemDevice(MSXDevice &device, int ps, int ss, int base, int size)
Devices can register themself in the MSX slotstructure.
void invalidateWCache(word start, unsigned size, int ps, int ss)
void unregisterMemDevice(MSXDevice &device, int ps, int ss, int base, int size)
void unregister_IO_Out(byte port, MSXDevice *device)
void fillRCache(unsigned start, unsigned size, const byte *rData, int ps, int ss)
bool isExpanded(int ps) const
An MSXDevice is an emulated hardware component connected to the bus of the emulated MSX.
Definition: MSXDevice.hh:32
MSXMotherBoard & getMotherBoard() const
Get the mother board this device belongs to.
Definition: MSXDevice.cc:76
virtual void init()
Definition: MSXDevice.cc:50
void invalidateDeviceRCache()
Definition: MSXDevice.hh:209
void fillDeviceWCache(unsigned start, unsigned size, byte *wData)
Definition: MSXDevice.cc:526
PluggingController & getPluggingController() const
Definition: MSXDevice.cc:164
virtual void powerUp(EmuTime::param time)
This method is called when MSX is powered up.
Definition: MSXDevice.cc:377
const DeviceConfig & getDeviceConfig2() const
Definition: MSXDevice.hh:233
void fillDeviceRWCache(unsigned start, unsigned size, byte *rwData)
Calls MSXCPUInterface::fillXXCache() for the specific (part of) the slot that this device is located ...
Definition: MSXDevice.cc:512
virtual ~MSXDevice()=0
Definition: MSXDevice.cc:59
CliComm & getCliComm() const
Definition: MSXDevice.cc:148
virtual const byte * getReadCacheLine(word start) const
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for reading.
Definition: MSXDevice.cc:432
void invalidateDeviceWCache()
Definition: MSXDevice.hh:210
void getDeviceInfo(TclObject &result) const
Get device info.
Definition: MSXDevice.cc:392
virtual void writeIO(word port, byte value, EmuTime::param time)
Write a byte to a given IO port at a certain time to this device.
Definition: MSXDevice.cc:415
virtual byte readIRQVector()
Gets IRQ vector used in IM2.
Definition: MSXDevice.cc:367
virtual void globalWrite(word address, byte value, EmuTime::param time)
Global writes.
Definition: MSXDevice.cc:455
Reactor & getReactor() const
Definition: MSXDevice.cc:152
virtual bool allowUnaligned() const
By default we don't allow unaligned <mem> specifications in the config file.
Definition: MSXDevice.hh:290
virtual unsigned getBaseSizeAlignment() const
The 'base' and 'size' attribute values need to be at least aligned to CacheLine::SIZE.
Definition: MSXDevice.cc:403
MSXDevice(const MSXDevice &)=delete
const XMLElement & getDeviceConfig() const
Get the configuration section for this device.
Definition: MSXDevice.hh:230
void testRemove(Devices alreadyRemoved) const
Checks whether this device can be removed (no other device has a reference to it).
Definition: MSXDevice.cc:81
std::vector< MSXDevice * > Devices
Definition: MSXDevice.hh:37
virtual byte readMem(word address, EmuTime::param time)
Read a byte from a location at a certain time from this device.
Definition: MSXDevice.cc:426
virtual void getExtraDeviceInfo(TclObject &result) const
Definition: MSXDevice.cc:398
void getVisibleMemRegion(unsigned &base, unsigned &size) const
Returns the range where this device is visible in memory.
Definition: MSXDevice.cc:310
virtual void writeMem(word address, byte value, EmuTime::param time)
Write a given byte to a given location at a certain time to this device.
Definition: MSXDevice.cc:437
virtual byte peekIO(word port, EmuTime::param time) const
Read a byte from a given IO port.
Definition: MSXDevice.cc:420
virtual byte peekMem(word address, EmuTime::param time) const
Read a byte from a given memory location.
Definition: MSXDevice.cc:443
MSXCPU & getCPU() const
Definition: MSXDevice.cc:136
const HardwareConfig & getHardwareConfig() const
Returns the hardwareconfig this device belongs to.
Definition: MSXDevice.hh:43
static byte unmappedRead[0x10000]
Definition: MSXDevice.hh:300
LedStatus & getLedStatus() const
Definition: MSXDevice.cc:160
void serialize(Archive &ar, unsigned version)
Definition: MSXDevice.cc:533
void invalidateDeviceRWCache()
Calls MSXCPUInterface::invalidateXXCache() for the specific (part of) the slot that this device is lo...
Definition: MSXDevice.hh:208
Scheduler & getScheduler() const
Definition: MSXDevice.cc:144
virtual void getNameList(TclObject &result) const
Returns list of name(s) of this device.
Definition: MSXDevice.cc:387
virtual byte * getWriteCacheLine(word start) const
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing.
Definition: MSXDevice.cc:466
void fillDeviceRCache(unsigned start, unsigned size, const byte *rData)
Definition: MSXDevice.cc:521
CommandController & getCommandController() const
Definition: MSXDevice.cc:156
const Devices & getReferences() const
Get the device references that are specified for this device.
Definition: MSXDevice.cc:126
virtual void powerDown(EmuTime::param time)
This method is called when MSX is powered down.
Definition: MSXDevice.cc:372
virtual void globalRead(word address, EmuTime::param time)
Global reads.
Definition: MSXDevice.cc:461
virtual std::string getName() const
Returns a human-readable name for this device.
Definition: MSXDevice.cc:382
virtual void reset(EmuTime::param time)
This method is called on reset.
Definition: MSXDevice.cc:362
EmuTime::param getCurrentTime() const
Definition: MSXDevice.cc:132
MSXCPUInterface & getCPUInterface() const
Definition: MSXDevice.cc:140
virtual byte readIO(word port, EmuTime::param time)
Read a byte from an IO port at a certain time from this device.
Definition: MSXDevice.cc:409
PluggingController & getPluggingController()
MSXCPUInterface & getCPUInterface()
EmuTime::param getCurrentTime()
Convenience method: This is the same as getScheduler().getCurrentTime().
CartridgeSlotManager & getSlotManager()
MSXDevice * findDevice(std::string_view name)
Find a MSXDevice by name.
CommandController & getCommandController()
Central administration of Connectors and Pluggables.
Contains the main loop of openMSX.
Definition: Reactor.hh:67
void addListElement(const T &t)
Definition: TclObject.hh:130
void addDictKeyValue(const Key &key, const Value &value)
Definition: TclObject.hh:144
constexpr bool ispow2(T x) noexcept
Is the given number an integral power of two? That is, does it have exactly one 1-bit in binary repre...
Definition: Math.hh:57
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:269
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:287
constexpr unsigned LOW
Definition: CacheLine.hh:9
constexpr unsigned HIGH
Definition: CacheLine.hh:10
constexpr unsigned SIZE
Definition: CacheLine.hh:7
This file implemented 3 utility functions:
Definition: Autofire.cc:5
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
auto set_difference(InputRange1 &&range1, InputRange2 &&range2, OutputIter out)
Definition: ranges.hh:243
void sort(RandomAccessRange &&range)
Definition: ranges.hh:35
size_t size(std::string_view utf8)
auto transform(Range &&range, UnaryOp op)
Definition: view.hh:306
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:983
auto rfind_unguarded(RANGE &range, const VAL &val)
Similar to the find(_if)_unguarded functions above, but searches from the back to front.
Definition: stl.hh:157
auto min_value(InputIterator first, InputIterator last)
Definition: stl.hh:253
auto max_value(InputIterator first, InputIterator last)
Definition: stl.hh:272
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:182
std::string strCat(Ts &&...ts)
Definition: strCat.hh:591
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:669
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr auto xrange(T e)
Definition: xrange.hh:155
auto end(const zstring_view &x)
Definition: zstring_view.hh:83