C++ Logo


Advanced search

Re: [SG13] Device info and acquisition: some suggestions of API design directions.

From: Tony V E <tvaneerd_at_[hidden]>
Date: Wed, 31 Jul 2019 13:16:34 -0400
On Wed, Jul 31, 2019 at 9:40 AM Klaim - Joël Lamotte via SG13 <
sg13_at_[hidden]> wrote:

> Hi all,
> I would like to suggest some directions to explore for some aspects of
> the proposals related to this group, when we need to represent
> devices.
> Feel free to point me papers and authors that already explored these
> directions.
> The following are just opinions of mine, developed while trying to
> design a standard library for input devices. Keep in mind that I'm not
> an expert in anything. (work on the input library is paused for now,
> not sure when I can resume it).
> This is mainly about devices acquisition and manipulation, so it's
> related the Audio library but also potentially any paper which tries
> to expose human-machine-interface devices (and maybe beyond?).
> I'm also suggesting below a potential solution for the API problem in
> the Audio library (how to allow using either WASAPI, ASIO or MME on
> Windows?).
> My points:
> 1. Device information is not acquisition: separate them.

There can be a high cost to acquisition, delay it.

2. Device information is plain data (aka value-semantic types).

+1 VOP (Value-oriented programming)

3. Device information should always be acquired through callbacks and
> helpers built around them.
Not always. For some devices, this would require polling, as the device
doesn't have a push model.
Also think of a device connected over a network - do you want a constant
data stream, or just get data when needed.

I guess pull-model devices could use a callback model where you set up the
callbacks and then call device.check_for_changes() or something like that.

> More details:
> When acquiring the existence, state and properties of devices, the
> information is always a snapshot acquired at a specific point in time
> and is always obsolete once acquired.
> Assuming this, several things could be done to ease management of
> devices through the lifetime of the software.
> The first and second point: information should be acquired through
> different types than the ones representing the device, that is for
> example, the style of the current audio API looks like this:
> auto device = acquire_some_device();
> if(device.name() == "that device")
> {
> do_some_work(device.property_a(), device.property_b()); // a
> might not be in sync with b or name.
> device.start(callback);
> }
> In this example, the device object might change state between and
> during each line. Therefore each accessor to each information might
> not be in sync, that is if they are relative to each other, it is
> impossible to acquire consistent synchronized state information
> corresponding to a specific point in time.
> I would recommend instead providing the information completely
> separately and as a whole-snapshot-of-data manner:
> auto device_info = get_some_device_info(); // all information are
> captured once, no device have been acquired.
> // data is already obsolete though, but that's a separate problem
> that can be dealt with now.
> if(device_info.name() == "that device")
> {
> do_some_work(device_info.a(), device_info.b()); // The data
> cannot have changed, even if the real state of the device changed in
> between.
> some_device device(device_info.id(), callback); // here
> construction = acquisition+start
> // ...
> }
> This would allow:
> - working on snapshot of data, for example for presenting
> information about the system without acquiring any device;
> - clarifying to the user the difference between acquiring
> information about the devices and acquiring access/control of that
> device, with 2 separate interfaces;
> - help the user maintain consistent information about devices by
> always providing the data as coherent complete snapshots.
> As a concrete example, currently the audio proposal defines functions
> like get_audio_input_device_list() which return audio_device_list.
> This is basically a sequence of audio_device, that you have to start()
> to actually acquire/begin-to-control. The same audio_device type is
> used to acquire data from the system which are fetched on demand by
> accessors or updated by some callback in the implementation. Data
> acquired this way might not be coherent if acquired through successive
> calls to several property accessors.
> The third point is about replacing any accessor function to device
> information by functions storing a callback.
> If all the data acquisition on devices were taking callbacks, code like
> this:
> auto value = device.some_property();
> work(value);
> would be replaced by:
> device.on_state_changed([=](device_info info){
> work(info.some_property()); }); // called at least once immediately
> or more realistically:
> device.on_state_changed([=](device_info info){
> const auto property_value = info.some_property();
> app.executor([=]{ work(property_value); });// make sure to
> work in the appropriate execution context
> });
> In this case, the callback should be called at least once with the
> current information, even if the information/state didn't change. This
> is important to keep the code depending on this information always up
> to date even if the data is not changing.
> In some cases one might just acquire the information once and ignore
> following states.
> Using awaitables and coroutines through a helper function would
> provide that easily:
> auto device_info = co_await device_state_snapshot(device); // this
> have to be a complete information snapshot
> where `device_state_snapshot` returns an awaitable, registers a
> callback and unregisters it as soon as it have been called once, then
> set the value to be returned.
> I wouldn't encourage this but it's better provided this way as it
> simplifies device interfaces, can be built over such a simple
> interface and imply that it is less expensive to just set a callback
> to react to the state instead of polling the information.
> Also, some other patterns could be implemented using such helper
> functions: capture info for n seconds and stop capturing, capture info
> only if it's consistent across several calls, etc.
> Combined, these API direction suggestions would also help providing a
> solution for the problem of selecting audio APIs (ASIO, MMX,
> DirectAudio etc. on Windows).
> Have the whole device status information provided as one snapshot of
> data that can be used to acquire devices. Audio APIs would then just
> be one layer of information.
> When the user want to acquire a device, the constructor or a separate
> acquisition function would take a combination, maybe optional, of the
> wanted device+api combination as one information represented by an
> identifier.
> For example:
> // First display a list of possible "API : device-name" list that
> can be selected.
> auto all_api_info = capture_audio_api_info(); // no device acquisition
> for(auto& api_info : all_api_info)
> {
> for(auto& device_info: api_info.devices()) // each info is
> relative to one API
> { // still no device acquired
> ui_audio_settings.present(fmt("{0} : {1}",
> api_info.name(), device_info.name());
> }
> }
> // Once the user select a device, aquire it and start working using it
> ui_audio_settings.on_device_selection([](auto device_info){ // the
> info is relative to one API
> const auto selected_device_id = device_info.id();
> app.some_executor.post([=]{ // where it's safe to do work,
> whatever the executor syntax is...
> auto maybe_device = acquire_audio_device(); // returns an
> optional<audio_device>);
> // or audio_device device(device_info.id());
> app.set_audio_device(device);
> });
> });
> End of suggestions. :)
> What do you think?
> A. Joël Lamotte
> --
> SG13 mailing list
> SG13_at_[hidden]
> http://lists.isocpp.org/mailman/listinfo.cgi/sg13

Be seeing you,

Received on 2019-07-31 12:18:45