mame/src/emu/output.h
Vas Crabb c3fb11c2c9 devcb3
There are multiple issues with the current device callbacks:
* They always dispatch through a pointer-to-member
* Chained callbacks are a linked list so the branch unit can't predict the early
* There's a runtime decision made on the left/right shift direction
* There are runtime NULL checks on various objects
* Binding a lambda isn't practical
* Arbitrary transformations are not supported
* When chaining callbacks it isn't clear what the MCFG_DEVCB_ modifiers apply to
* It isn't possible to just append to a callback in derived configuration
* The macros need a magic, hidden local called devcb
* Moving code that uses the magic locals around is error-prone
* Writing the MCFG_ macros to make a device usable is a pain
* You can't discover applicable MCFG_ macros with intellisense
* Macros are not scoped
* Using an inappropriate macro isn't detected at compile time
* Lots of other things

This changeset overcomes the biggest obstacle to remving MCFG_ macros
altogether.  Essentially, to allow a devcb to be configured, call
.bind() and expose the result (a bind target for the callback).  Bind
target methods starting with "set" repace the current callbacks; methods
starting with "append" append to them.  You can't reconfigure a callback
after resolving it.  There's no need to use a macro matching the
handler signatures - use FUNC for everything.  Current device is implied
if no tag/finder is supplied (no need for explicit this).

Lambdas are supported, and the memory space and offset are optional.
These kinds of things work:
* .read_cb().set([this] () { return something; });
* .read_cb().set([this] (offs_t offset) { return ~offset; });
* .write_cb().set([this] (offs_t offset, u8 data) { m_array[offset] = data; });
* .write_cb().set([this] (int state) { some_var = state; });

Arbitrary transforms are allowed, and they can modify offset/mask for example:
* .read_cb().set(FUNC(my_state::handler)).transform([] (u8 data) { return bitswap<4>(data, 1, 3, 0, 2); });
* .read_cb().set(m_dev, FUNC(some_device::member)).transform([] (offs_t &offset, u8 data) { offset ^= 3; return data; });

It's possible to stack arbitrary transforms, at the cost of compile
time (the whole transform stack gets inlined at compile time).  Shifts
count as an arbitrary transform, but mask/exor does not.

Order of mask/shift/exor now matters.  Modifications are applied in the
specified order.  These are NOT EQUIVALENT:
* .read_cb().set(FUNC(my_state::handler)).mask(0x06).lshift(2);
* .read_cb().set(FUNC(my_state::handler)).lshift(2).mask(0x06);

The bit helper no longer reverses its behaviour for read callbacks, and
I/O ports are no longer aware of the field mask.  Binding a read
callback to no-op is not supported - specify a constant.  The GND and
VCC aliases have been removed intentionally - they're TTL-centric, and
were already being abused.

Other quirks have been preserved, including write logger only logging
when the data is non-zero (quite unhelpful in many of the cases where
it's used).  Legacy syntax is still supported for simple cases, but will
be phased out.  New devices should not have MCFG_ macros.

I don't think I've missed any fundamental issues, but if I've broken
something, let me know.
2018-07-07 02:40:29 +10:00

203 lines
6.1 KiB
C++

// license:BSD-3-Clause
// copyright-holders:Nicola Salmoria, Aaron Giles, Vas Crabb
/***************************************************************************
output.h
General purpose output routines.
***************************************************************************/
#pragma once
#ifndef __EMU_H__
#error Dont include this file directly; include emu.h instead.
#endif
#ifndef MAME_EMU_OUTPUT_H
#define MAME_EMU_OUTPUT_H
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
typedef void (*output_notifier_func)(const char *outname, s32 value, void *param);
// ======================> output_manager
class output_manager
{
private:
template <typename Input, std::make_unsigned_t<Input> DefaultMask> friend class devcb_write;
class output_notify
{
public:
output_notify(output_notifier_func callback, void *param)
: m_notifier(callback)
, m_param(param)
{
}
void operator()(char const *outname, s32 value) const { m_notifier(outname, value, m_param); }
private:
output_notifier_func m_notifier; // callback to call
void * m_param; // parameter to pass the callback
};
using notify_vector = std::vector<output_notify>;
class output_item
{
public:
output_item(output_item &&) = delete;
output_item(output_item const &) = delete;
output_item &operator=(output_item &&) = delete;
output_item &operator=(output_item const &) = delete;
output_item(
output_manager &manager,
std::string &&name,
u32 id,
s32 value);
std::string const &name() const { return m_name; }
u32 id() const { return m_id; }
s32 get() const { return m_value; }
void set(s32 value) { if (m_value != value) { notify(value); } }
void notify(s32 value);
void set_notifier(output_notifier_func callback, void *param) { m_notifylist.emplace_back(callback, param); }
private:
output_manager &m_manager; // parent output manager
std::string const m_name; // string name of the item
u32 const m_id; // unique ID for this item
s32 m_value; // current value
notify_vector m_notifylist; // list of notifier callbacks
};
class item_proxy
{
public:
item_proxy() = default;
void resolve(device_t &device, std::string const &name);
operator s32() const { return m_item->get(); }
s32 operator=(s32 value) { m_item->set(value); return m_item->get(); }
private:
output_item *m_item = nullptr;
};
template <unsigned M, unsigned... N> struct item_proxy_array { typedef typename item_proxy_array<N...>::type type[M]; };
template <unsigned N> struct item_proxy_array<N> { typedef item_proxy type[N]; };
template <unsigned... N> using item_proxy_array_t = typename item_proxy_array<N...>::type;
public:
template <typename X, unsigned... N> class output_finder
{
public:
template <typename... T> output_finder(device_t &device, std::string &&format, T &&... start_args)
: m_device(device)
, m_format(std::move(format))
, m_start_args{ std::forward<T>(start_args)... }
{
}
auto &operator[](unsigned n) { return m_proxies[n]; }
auto &operator[](unsigned n) const { return m_proxies[n]; }
auto begin() { return std::begin(m_proxies); }
auto end() { return std::end(m_proxies); }
auto begin() const { return std::begin(m_proxies); }
auto end() const { return std::end(m_proxies); }
auto cbegin() const { return std::begin(m_proxies); }
auto cend() const { return std::end(m_proxies); }
void resolve() { resolve<0U>(m_proxies); }
private:
template <unsigned A, unsigned C, typename... T>
void resolve(item_proxy (&proxies)[C], T &&... i)
{
for (unsigned j = 0U; C > j; ++j)
proxies[j].resolve(m_device, util::string_format(m_format, std::forward<T>(i)..., j + m_start_args[A]));
}
template <unsigned A, unsigned C, unsigned D, typename T, typename... U>
void resolve(T (&proxies)[C][D], U &&... i)
{
for (unsigned j = 0U; C > j; ++j)
resolve<A + 1>(proxies[j], std::forward<U>(i)..., j + m_start_args[A]);
}
device_t &m_device;
std::string const m_format;
unsigned const m_start_args[sizeof...(N)];
item_proxy_array_t<N...> m_proxies;
};
template <typename X> class output_finder<X>
{
public:
output_finder(device_t &device, std::string &&format)
: m_device(device)
, m_format(std::move(format))
{
}
operator s32() const { return m_proxy; }
s32 operator=(s32 value) { return m_proxy = value; }
void resolve() { m_proxy.resolve(m_device, m_format); }
private:
device_t &m_device;
std::string const m_format;
item_proxy m_proxy;
};
// construction/destruction
output_manager(running_machine &machine);
// getters
running_machine &machine() const { return m_machine; }
// set the value for a given output
void set_value(const char *outname, s32 value);
// return the current value for a given output
s32 get_value(const char *outname);
// set a notifier on a particular output, or globally if nullptr
void set_notifier(const char *outname, output_notifier_func callback, void *param);
// set a notifier on a particular output, or globally if nullptr
void notify_all(output_module *module);
// map a name to a unique ID
u32 name_to_id(const char *outname);
// map a unique ID back to a name
const char *id_to_name(u32 id);
void pause();
void resume();
private:
// set an indexed value for an output (concatenates basename + index)
void set_indexed_value(const char *basename, int index, int value);
output_item *find_item(const char *string);
output_item &create_new_item(const char *outname, s32 value);
output_item &find_or_create_item(const char *outname, s32 value);
// internal state
running_machine &m_machine; // reference to our machine
std::unordered_map<std::string, output_item> m_itemtable;
notify_vector m_global_notifylist;
u32 m_uniqueid;
};
template <unsigned... N> using output_finder = output_manager::output_finder<void, N...>;
#endif // MAME_EMU_OUTPUT_H