mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-07 05:30:30 +00:00
Merge pull request #1065 from TomHarte/QueueShakeup
Consolidate/simplify queue classes.
This commit is contained in:
commit
3d6ce6c13f
@ -47,7 +47,7 @@ template <typename MachineType> class MultiInterface {
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
|
||||
private:
|
||||
std::vector<Concurrency::AsyncTaskQueue> queues_;
|
||||
std::vector<Concurrency::TaskQueue<true>> queues_;
|
||||
};
|
||||
|
||||
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
|
||||
|
@ -315,7 +315,7 @@ template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = Lo
|
||||
LocalTimeScale time_since_update_;
|
||||
TargetTimeScale threshold_;
|
||||
bool is_flushed_ = true;
|
||||
Concurrency::AsyncTaskQueue task_queue_;
|
||||
Concurrency::TaskQueue<true> task_queue_;
|
||||
};
|
||||
|
||||
#endif /* JustInTime_h */
|
||||
|
@ -12,18 +12,18 @@
|
||||
|
||||
using namespace MOS::MOS6560;
|
||||
|
||||
AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
AudioGenerator::AudioGenerator(Concurrency::TaskQueue<false> &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
|
||||
void AudioGenerator::set_volume(uint8_t volume) {
|
||||
audio_queue_.defer([this, volume]() {
|
||||
audio_queue_.enqueue([this, volume]() {
|
||||
volume_ = int16_t(volume) * range_multiplier_;
|
||||
});
|
||||
}
|
||||
|
||||
void AudioGenerator::set_control(int channel, uint8_t value) {
|
||||
audio_queue_.defer([this, channel, value]() {
|
||||
audio_queue_.enqueue([this, channel, value]() {
|
||||
control_registers_[channel] = value;
|
||||
});
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ namespace MOS6560 {
|
||||
// audio state
|
||||
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
AudioGenerator(Concurrency::TaskQueue<false> &audio_queue);
|
||||
|
||||
void set_volume(uint8_t volume);
|
||||
void set_control(int channel, uint8_t value);
|
||||
@ -33,7 +33,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
Concurrency::TaskQueue<false> &audio_queue_;
|
||||
|
||||
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
|
||||
unsigned int shift_registers_[4] = {0, 0, 0, 0};
|
||||
@ -433,7 +433,7 @@ template <class BusHandler> class MOS6560 {
|
||||
BusHandler &bus_handler_;
|
||||
Outputs::CRT::CRT crt_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
AudioGenerator audio_generator_;
|
||||
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
using namespace GI::AY38910;
|
||||
|
||||
template <bool is_stereo>
|
||||
AY38910<is_stereo>::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
|
||||
AY38910<is_stereo>::AY38910(Personality personality, Concurrency::TaskQueue<false> &task_queue) : task_queue_(task_queue) {
|
||||
// Don't use the low bit of the envelope position if this is an AY.
|
||||
envelope_position_mask_ |= personality == Personality::AY38910;
|
||||
|
||||
@ -252,7 +252,7 @@ template <bool is_stereo> void AY38910<is_stereo>::set_register_value(uint8_t va
|
||||
// If this is a register that affects audio output, enqueue a mutation onto the
|
||||
// audio generation thread.
|
||||
if(selected_register_ < 14) {
|
||||
task_queue_.defer([this, selected_register = selected_register_, value] () {
|
||||
task_queue_.enqueue([this, selected_register = selected_register_, value] () {
|
||||
// Perform any register-specific mutation to output generation.
|
||||
uint8_t masked_value = value;
|
||||
switch(selected_register) {
|
||||
|
@ -71,7 +71,7 @@ enum class Personality {
|
||||
template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
/// Creates a new AY38910.
|
||||
AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &);
|
||||
AY38910(Personality, Concurrency::TaskQueue<false> &);
|
||||
|
||||
/// Sets the value the AY would read from its data lines if it were not outputting.
|
||||
void set_data_input(uint8_t r);
|
||||
@ -114,7 +114,7 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
|
||||
static constexpr bool get_is_stereo() { return is_stereo; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
Concurrency::TaskQueue<false> &task_queue_;
|
||||
|
||||
int selected_register_ = 0;
|
||||
uint8_t registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
using namespace Audio;
|
||||
|
||||
Audio::Toggle::Toggle(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
Audio::Toggle::Toggle(Concurrency::TaskQueue<false> &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void Toggle::get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
@ -28,7 +28,7 @@ void Toggle::skip_samples(std::size_t) {}
|
||||
void Toggle::set_output(bool enabled) {
|
||||
if(is_enabled_ == enabled) return;
|
||||
is_enabled_ = enabled;
|
||||
audio_queue_.defer([this, enabled] {
|
||||
audio_queue_.enqueue([this, enabled] {
|
||||
level_ = enabled ? volume_ : 0;
|
||||
});
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace Audio {
|
||||
*/
|
||||
class Toggle: public Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
Toggle(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
Toggle(Concurrency::TaskQueue<false> &audio_queue);
|
||||
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
@ -31,7 +31,7 @@ class Toggle: public Outputs::Speaker::SampleSource {
|
||||
private:
|
||||
// Accessed on the calling thread.
|
||||
bool is_enabled_ = false;
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
Concurrency::TaskQueue<false> &audio_queue_;
|
||||
|
||||
// Accessed on the audio thread.
|
||||
int16_t level_ = 0, volume_ = 0;
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
using namespace Konami;
|
||||
|
||||
SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) :
|
||||
SCC::SCC(Concurrency::TaskQueue<false> &task_queue) :
|
||||
task_queue_(task_queue) {}
|
||||
|
||||
bool SCC::is_zero_level() const {
|
||||
@ -55,7 +55,7 @@ void SCC::write(uint16_t address, uint8_t value) {
|
||||
address &= 0xff;
|
||||
if(address < 0x80) ram_[address] = value;
|
||||
|
||||
task_queue_.defer([this, address, value] {
|
||||
task_queue_.enqueue([this, address, value] {
|
||||
// Check for a write into waveform memory.
|
||||
if(address < 0x80) {
|
||||
waves_[address >> 5].samples[address & 0x1f] = value;
|
||||
|
@ -24,7 +24,7 @@ namespace Konami {
|
||||
class SCC: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
/// Creates a new SCC.
|
||||
SCC(Concurrency::DeferringAsyncTaskQueue &task_queue);
|
||||
SCC(Concurrency::TaskQueue<false> &task_queue);
|
||||
|
||||
/// As per ::SampleSource; provides a broadphase test for silence.
|
||||
bool is_zero_level() const;
|
||||
@ -41,7 +41,7 @@ class SCC: public ::Outputs::Speaker::SampleSource {
|
||||
uint8_t read(uint16_t address);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
Concurrency::TaskQueue<false> &task_queue_;
|
||||
|
||||
// State from here on down is accessed ony from the audio thread.
|
||||
int master_divider_ = 0;
|
||||
|
@ -26,9 +26,9 @@ template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource
|
||||
}
|
||||
|
||||
protected:
|
||||
OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
|
||||
OPLBase(Concurrency::TaskQueue<false> &task_queue) : task_queue_(task_queue) {}
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
Concurrency::TaskQueue<false> &task_queue_;
|
||||
|
||||
private:
|
||||
uint8_t selected_register_ = 0;
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
using namespace Yamaha::OPL;
|
||||
|
||||
OPLL::OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider, bool is_vrc7):
|
||||
OPLL::OPLL(Concurrency::TaskQueue<false> &task_queue, int audio_divider, bool is_vrc7):
|
||||
OPLBase(task_queue), audio_divider_(audio_divider), is_vrc7_(is_vrc7) {
|
||||
// Due to the way that sound mixing works on the OPLL, the audio divider may not
|
||||
// be larger than 4.
|
||||
@ -74,7 +74,7 @@ OPLL::OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider,
|
||||
void OPLL::write_register(uint8_t address, uint8_t value) {
|
||||
// The OPLL doesn't have timers or other non-audio functions, so all writes
|
||||
// go to the audio queue.
|
||||
task_queue_.defer([this, address, value] {
|
||||
task_queue_.enqueue([this, address, value] {
|
||||
// The first 8 locations are used to define the custom instrument, and have
|
||||
// exactly the same format as the patch set arrays at the head of this file.
|
||||
if(address < 8) {
|
||||
|
@ -24,7 +24,7 @@ namespace OPL {
|
||||
class OPLL: public OPLBase<OPLL> {
|
||||
public:
|
||||
/// Creates a new OPLL or VRC7.
|
||||
OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider = 1, bool is_vrc7 = false);
|
||||
OPLL(Concurrency::TaskQueue<false> &task_queue, int audio_divider = 1, bool is_vrc7 = false);
|
||||
|
||||
/// As per ::SampleSource; provides audio output.
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
using namespace TI;
|
||||
|
||||
SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) {
|
||||
SN76489::SN76489(Personality personality, Concurrency::TaskQueue<false> &task_queue, int additional_divider) : task_queue_(task_queue) {
|
||||
set_sample_volume_range(0);
|
||||
|
||||
switch(personality) {
|
||||
@ -49,7 +49,7 @@ void SN76489::set_sample_volume_range(std::int16_t range) {
|
||||
}
|
||||
|
||||
void SN76489::write(uint8_t value) {
|
||||
task_queue_.defer([value, this] () {
|
||||
task_queue_.enqueue([value, this] () {
|
||||
if(value & 0x80) {
|
||||
active_register_ = value;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class SN76489: public Outputs::Speaker::SampleSource {
|
||||
};
|
||||
|
||||
/// Creates a new SN76489.
|
||||
SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider = 1);
|
||||
SN76489(Personality personality, Concurrency::TaskQueue<false> &task_queue, int additional_divider = 1);
|
||||
|
||||
/// Writes a new value to the SN76489.
|
||||
void write(uint8_t value);
|
||||
@ -41,7 +41,7 @@ class SN76489: public Outputs::Speaker::SampleSource {
|
||||
void evaluate_output_volume();
|
||||
int volumes_[16];
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
Concurrency::TaskQueue<false> &task_queue_;
|
||||
|
||||
struct ToneChannel {
|
||||
// Programmatically-set state; updated by the processor.
|
||||
|
@ -1,110 +0,0 @@
|
||||
//
|
||||
// AsyncTaskQueue.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/10/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "AsyncTaskQueue.hpp"
|
||||
|
||||
using namespace Concurrency;
|
||||
|
||||
AsyncTaskQueue::AsyncTaskQueue()
|
||||
#ifndef USE_GCD
|
||||
:
|
||||
should_destruct_(false),
|
||||
thread_([this] () {
|
||||
while(!should_destruct_) {
|
||||
std::function<void(void)> next_function;
|
||||
|
||||
// Take lock, check for a new task.
|
||||
std::unique_lock lock(queue_mutex_);
|
||||
if(!pending_tasks_.empty()) {
|
||||
next_function = pending_tasks_.front();
|
||||
pending_tasks_.pop_front();
|
||||
}
|
||||
|
||||
if(next_function) {
|
||||
// If there is a task, release lock and perform it.
|
||||
lock.unlock();
|
||||
next_function();
|
||||
} else {
|
||||
// If there isn't a task, atomically block on the processing condition and release the lock
|
||||
// until there's something pending (and then release it again via scope).
|
||||
processing_condition_.wait(lock);
|
||||
}
|
||||
}
|
||||
})
|
||||
#else
|
||||
: serial_dispatch_queue_(dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL))
|
||||
#endif
|
||||
{}
|
||||
|
||||
AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
#ifdef USE_GCD
|
||||
flush();
|
||||
dispatch_release(serial_dispatch_queue_);
|
||||
#else
|
||||
// Set should destruct, and then give the thread a bit of a nudge
|
||||
// via an empty enqueue.
|
||||
should_destruct_ = true;
|
||||
enqueue([](){});
|
||||
|
||||
// Wait for the thread safely to terminate.
|
||||
thread_.join();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
|
||||
#ifdef USE_GCD
|
||||
dispatch_async(serial_dispatch_queue_, ^{function();});
|
||||
#else
|
||||
std::lock_guard lock(queue_mutex_);
|
||||
pending_tasks_.push_back(function);
|
||||
processing_condition_.notify_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::flush() {
|
||||
#ifdef USE_GCD
|
||||
dispatch_sync(serial_dispatch_queue_, ^{});
|
||||
#else
|
||||
auto flush_mutex = std::make_shared<std::mutex>();
|
||||
auto flush_condition = std::make_shared<std::condition_variable>();
|
||||
std::unique_lock lock(*flush_mutex);
|
||||
enqueue([=] () {
|
||||
std::unique_lock inner_lock(*flush_mutex);
|
||||
flush_condition->notify_all();
|
||||
});
|
||||
flush_condition->wait(lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
|
||||
perform();
|
||||
flush();
|
||||
}
|
||||
|
||||
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
|
||||
if(!deferred_tasks_) {
|
||||
deferred_tasks_ = std::make_unique<TaskList>();
|
||||
deferred_tasks_->reserve(16);
|
||||
}
|
||||
deferred_tasks_->push_back(function);
|
||||
}
|
||||
|
||||
void DeferringAsyncTaskQueue::perform() {
|
||||
if(!deferred_tasks_) return;
|
||||
enqueue([deferred_tasks_raw = deferred_tasks_.release()] {
|
||||
std::unique_ptr<TaskList> deferred_tasks(deferred_tasks_raw);
|
||||
for(const auto &function : *deferred_tasks) {
|
||||
function();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DeferringAsyncTaskQueue::flush() {
|
||||
perform();
|
||||
AsyncTaskQueue::flush();
|
||||
}
|
@ -12,11 +12,11 @@
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "../ClockReceiver/TimeTypes.hpp"
|
||||
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
#include <dispatch/dispatch.h>
|
||||
#define USE_GCD
|
||||
@ -24,81 +24,159 @@
|
||||
|
||||
namespace Concurrency {
|
||||
|
||||
using TaskList = std::vector<std::function<void(void)>>;
|
||||
/// An implementation detail; provides the time-centric part of a TaskQueue with a real Performer.
|
||||
template <typename Performer> struct TaskQueueStorage {
|
||||
template <typename... Args> TaskQueueStorage(Args&&... args) :
|
||||
performer(std::forward<Args>(args)...),
|
||||
last_fired_(Time::nanos_now()) {}
|
||||
|
||||
/*!
|
||||
An async task queue allows a caller to enqueue void(void) functions. Those functions are guaranteed
|
||||
to be performed serially and asynchronously from the caller. A caller may also request to flush,
|
||||
causing it to block until all previously-enqueued functions are complete.
|
||||
*/
|
||||
class AsyncTaskQueue {
|
||||
public:
|
||||
AsyncTaskQueue();
|
||||
virtual ~AsyncTaskQueue();
|
||||
Performer performer;
|
||||
|
||||
/*!
|
||||
Adds @c function to the queue.
|
||||
|
||||
@discussion Functions will be performed serially and asynchronously. This method is safe to
|
||||
call from multiple threads.
|
||||
@parameter function The function to enqueue.
|
||||
*/
|
||||
void enqueue(std::function<void(void)> function);
|
||||
|
||||
/*!
|
||||
Blocks the caller until all previously-enqueud functions have completed.
|
||||
*/
|
||||
void flush();
|
||||
protected:
|
||||
void update() {
|
||||
auto time_now = Time::nanos_now();
|
||||
performer.perform(time_now - last_fired_);
|
||||
last_fired_ = time_now;
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef USE_GCD
|
||||
dispatch_queue_t serial_dispatch_queue_;
|
||||
#else
|
||||
std::atomic_bool should_destruct_;
|
||||
std::condition_variable processing_condition_;
|
||||
std::mutex queue_mutex_;
|
||||
std::list<std::function<void(void)>> pending_tasks_;
|
||||
Time::Nanos last_fired_;
|
||||
};
|
||||
|
||||
std::thread thread_;
|
||||
#endif
|
||||
/// An implementation detail; provides a no-op implementation of time advances for TaskQueues without a Performer.
|
||||
template <> struct TaskQueueStorage<int> {
|
||||
TaskQueueStorage() {}
|
||||
|
||||
protected:
|
||||
void update() {}
|
||||
};
|
||||
|
||||
/*!
|
||||
A deferring async task queue is one that accepts a list of functions to be performed but defers
|
||||
any action until told to perform. It performs them by enquing a single asynchronous task that will
|
||||
perform the deferred tasks in order.
|
||||
A task queue allows a caller to enqueue void(void) functions. Those functions are guaranteed
|
||||
to be performed serially and asynchronously from the caller.
|
||||
|
||||
It therefore offers similar semantics to an asynchronous task queue, but allows for management of
|
||||
synchronisation costs, since neither defer nor perform make any effort to be thread safe.
|
||||
If @c perform_automatically is true, functions will be performed as soon as is possible,
|
||||
at the cost of thread synchronisation.
|
||||
|
||||
If @c perform_automatically is false, functions will be queued up and not dispatched
|
||||
until a call to perform().
|
||||
|
||||
If a @c Performer type is supplied then a public member, @c performer will be constructed
|
||||
with the arguments supplied to TaskQueue's constructor, and that class will receive calls of the
|
||||
form @c .perform(nanos) to update it to every batch of new actions.
|
||||
*/
|
||||
class DeferringAsyncTaskQueue: public AsyncTaskQueue {
|
||||
template <bool perform_automatically, typename Performer = int> class TaskQueue: public TaskQueueStorage<Performer> {
|
||||
public:
|
||||
~DeferringAsyncTaskQueue();
|
||||
template <typename... Args> TaskQueue(Args&&... args) :
|
||||
TaskQueueStorage<Performer>(std::forward<Args>(args)...),
|
||||
thread_{
|
||||
[this] {
|
||||
ActionVector actions;
|
||||
|
||||
/*!
|
||||
Adds a function to the deferral list.
|
||||
while(!should_quit) {
|
||||
// Wait for new actions to be signalled, and grab them.
|
||||
std::unique_lock lock(condition_mutex_);
|
||||
while(actions_.empty()) {
|
||||
condition_.wait(lock);
|
||||
}
|
||||
std::swap(actions, actions_);
|
||||
lock.unlock();
|
||||
|
||||
This is not thread safe; it should be serialised with other calls to itself and to perform.
|
||||
*/
|
||||
void defer(std::function<void(void)> function);
|
||||
// Update to now (which is possibly a no-op).
|
||||
TaskQueueStorage<Performer>::update();
|
||||
|
||||
/*!
|
||||
Enqueues a function that will perform all currently deferred functions, in the
|
||||
order that they were deferred.
|
||||
// Perform the actions and destroy them.
|
||||
for(const auto &action: actions) {
|
||||
action();
|
||||
}
|
||||
actions.clear();
|
||||
}
|
||||
}
|
||||
} {}
|
||||
|
||||
This is not thread safe; it should be serialised with other calls to itself and to defer.
|
||||
*/
|
||||
void perform();
|
||||
/// Enqueus @c post_action to be performed asynchronously at some point
|
||||
/// in the future. If @c perform_automatically is @c true then the action
|
||||
/// will be performed as soon as possible. Otherwise it will sit unsheculed until
|
||||
/// a call to @c perform().
|
||||
///
|
||||
/// Actions may be elided.
|
||||
///
|
||||
/// If this TaskQueue has a @c Performer then the action will be performed
|
||||
/// on the same thread as the performer, after the performer has been updated
|
||||
/// to 'now'.
|
||||
void enqueue(const std::function<void(void)> &post_action) {
|
||||
std::lock_guard guard(condition_mutex_);
|
||||
actions_.push_back(post_action);
|
||||
|
||||
/*!
|
||||
Blocks the caller until all previously-enqueud functions have completed.
|
||||
*/
|
||||
void flush();
|
||||
if constexpr (perform_automatically) {
|
||||
condition_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
/// Causes any enqueued actions that are not yet scheduled to be scheduled.
|
||||
void perform() {
|
||||
if(actions_.empty()) {
|
||||
return;
|
||||
}
|
||||
condition_.notify_all();
|
||||
}
|
||||
|
||||
/// Permanently stops this task queue, blocking until that has happened.
|
||||
/// All pending actions will be performed first.
|
||||
///
|
||||
/// The queue cannot be restarted; this is a destructive action.
|
||||
void stop() {
|
||||
if(thread_.joinable()) {
|
||||
should_quit = true;
|
||||
enqueue([] {});
|
||||
if constexpr (!perform_automatically) {
|
||||
perform();
|
||||
}
|
||||
thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedules any remaining unscheduled work, then blocks synchronously
|
||||
/// until all scheduled work has been performed.
|
||||
void flush() {
|
||||
std::mutex flush_mutex;
|
||||
std::condition_variable flush_condition;
|
||||
bool has_run = false;
|
||||
std::unique_lock lock(flush_mutex);
|
||||
|
||||
enqueue([&flush_mutex, &flush_condition, &has_run] () {
|
||||
std::unique_lock inner_lock(flush_mutex);
|
||||
has_run = true;
|
||||
flush_condition.notify_all();
|
||||
});
|
||||
|
||||
if constexpr (!perform_automatically) {
|
||||
perform();
|
||||
}
|
||||
|
||||
flush_condition.wait(lock, [&has_run] { return has_run; });
|
||||
}
|
||||
|
||||
~TaskQueue() {
|
||||
stop();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<TaskList> deferred_tasks_;
|
||||
// The list of actions waiting be performed. These will be elided,
|
||||
// increasing their latency, if the emulation thread falls behind.
|
||||
using ActionVector = std::vector<std::function<void(void)>>;
|
||||
ActionVector actions_;
|
||||
|
||||
// Necessary synchronisation parts.
|
||||
std::atomic<bool> should_quit = false;
|
||||
std::mutex condition_mutex_;
|
||||
std::condition_variable condition_;
|
||||
|
||||
// Ensure the thread isn't constructed until after the mutex
|
||||
// and condition variable.
|
||||
std::thread thread_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Concurrency_hpp */
|
||||
#endif /* AsyncTaskQueue_hpp */
|
||||
|
@ -1,99 +0,0 @@
|
||||
//
|
||||
// AsyncUpdater.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/07/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AsyncUpdater_hpp
|
||||
#define AsyncUpdater_hpp
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include "../ClockReceiver/TimeTypes.hpp"
|
||||
|
||||
namespace Concurrency {
|
||||
|
||||
template <typename Performer> class AsyncUpdater {
|
||||
public:
|
||||
template <typename... Args> AsyncUpdater(Args&&... args) :
|
||||
performer(std::forward<Args>(args)...),
|
||||
actions_(std::make_unique<ActionVector>()),
|
||||
performer_thread_{
|
||||
[this] {
|
||||
Time::Nanos last_fired = Time::nanos_now();
|
||||
auto actions = std::make_unique<ActionVector>();
|
||||
|
||||
while(!should_quit) {
|
||||
// Wait for new actions to be signalled, and grab them.
|
||||
std::unique_lock lock(condition_mutex_);
|
||||
while(actions_->empty()) {
|
||||
condition_.wait(lock);
|
||||
}
|
||||
std::swap(actions, actions_);
|
||||
lock.unlock();
|
||||
|
||||
// Update to now.
|
||||
auto time_now = Time::nanos_now();
|
||||
performer.perform(time_now - last_fired);
|
||||
last_fired = time_now;
|
||||
|
||||
// Perform the actions.
|
||||
for(const auto& action: *actions) {
|
||||
action();
|
||||
}
|
||||
actions->clear();
|
||||
}
|
||||
}
|
||||
} {}
|
||||
|
||||
/// Run the performer up to 'now' and then perform @c post_action.
|
||||
///
|
||||
/// @c post_action will be performed asynchronously, on the same
|
||||
/// thread as the performer.
|
||||
///
|
||||
/// Actions may be elided,
|
||||
void update(const std::function<void(void)> &post_action) {
|
||||
std::lock_guard guard(condition_mutex_);
|
||||
actions_->push_back(post_action);
|
||||
condition_.notify_all();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if(performer_thread_.joinable()) {
|
||||
should_quit = true;
|
||||
update([] {});
|
||||
performer_thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
~AsyncUpdater() {
|
||||
stop();
|
||||
}
|
||||
|
||||
// The object that will actually receive time advances.
|
||||
Performer performer;
|
||||
|
||||
private:
|
||||
// The list of actions waiting be performed. These will be elided,
|
||||
// increasing their latency, if the emulation thread falls behind.
|
||||
using ActionVector = std::vector<std::function<void(void)>>;
|
||||
std::unique_ptr<ActionVector> actions_;
|
||||
|
||||
// Necessary synchronisation parts.
|
||||
std::atomic<bool> should_quit = false;
|
||||
std::mutex condition_mutex_;
|
||||
std::condition_variable condition_;
|
||||
|
||||
// Ensure the thread isn't constructed until after the mutex
|
||||
// and condition variable.
|
||||
std::thread performer_thread_;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif /* AsyncUpdater_hpp */
|
@ -149,7 +149,7 @@ class Audio: public DMADevice<4> {
|
||||
|
||||
// Transient output state, and its destination.
|
||||
Outputs::Speaker::PushLowpass<true> speaker_;
|
||||
Concurrency::AsyncTaskQueue queue_;
|
||||
Concurrency::TaskQueue<true> queue_;
|
||||
|
||||
using AudioBuffer = std::array<int16_t, 4096>;
|
||||
static constexpr int BufferCount = 3;
|
||||
|
@ -156,7 +156,7 @@ class AYDeferrer {
|
||||
}
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
GI::AY38910::AY38910<true> ay_;
|
||||
Outputs::Speaker::PullLowpass<GI::AY38910::AY38910<true>> speaker_;
|
||||
HalfCycles cycles_since_update_;
|
||||
|
@ -95,7 +95,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
uint8_t ram_[65536], aux_ram_[65536];
|
||||
std::vector<uint8_t> rom_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
Audio::Toggle audio_toggle_;
|
||||
Outputs::Speaker::PullLowpass<Audio::Toggle> speaker_;
|
||||
Cycles cycles_since_audio_update_;
|
||||
|
@ -1150,7 +1150,7 @@ class ConcreteMachine:
|
||||
Apple::Disk::DiskIIDrive drives525_[2];
|
||||
|
||||
// The audio parts.
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
Apple::IIgs::Sound::GLU sound_glu_;
|
||||
Audio::Toggle audio_toggle_;
|
||||
using AudioSource = Outputs::Speaker::CompoundSource<Apple::IIgs::Sound::GLU, Audio::Toggle>;
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
using namespace Apple::IIgs::Sound;
|
||||
|
||||
GLU::GLU(Concurrency::DeferringAsyncTaskQueue &audio_queue) : audio_queue_(audio_queue) {
|
||||
GLU::GLU(Concurrency::TaskQueue<false> &audio_queue) : audio_queue_(audio_queue) {
|
||||
// Reset all pending stores.
|
||||
MemoryWrite disabled_write;
|
||||
disabled_write.enabled = false;
|
||||
@ -42,7 +42,7 @@ void GLU::set_data(uint8_t data) {
|
||||
// Register access.
|
||||
const auto address = address_; // To make sure I don't inadvertently 'capture' address_.
|
||||
local_.set_register(address, data);
|
||||
audio_queue_.defer([this, address, data] () {
|
||||
audio_queue_.enqueue([this, address, data] () {
|
||||
remote_.set_register(address, data);
|
||||
});
|
||||
}
|
||||
@ -191,7 +191,7 @@ void GLU::set_sample_volume_range(std::int16_t range) {
|
||||
|
||||
void GLU::set_control(uint8_t control) {
|
||||
local_.control = control;
|
||||
audio_queue_.defer([this, control] () {
|
||||
audio_queue_.enqueue([this, control] () {
|
||||
remote_.control = control;
|
||||
});
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ namespace Sound {
|
||||
|
||||
class GLU: public Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
GLU(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
GLU(Concurrency::TaskQueue<false> &audio_queue);
|
||||
|
||||
void set_control(uint8_t);
|
||||
uint8_t get_control();
|
||||
@ -42,7 +42,7 @@ class GLU: public Outputs::Speaker::SampleSource {
|
||||
void skip_samples(const std::size_t number_of_samples);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
Concurrency::TaskQueue<false> &audio_queue_;
|
||||
|
||||
uint16_t address_ = 0;
|
||||
|
||||
|
@ -18,7 +18,7 @@ const std::size_t sample_length = 352 / 2;
|
||||
|
||||
}
|
||||
|
||||
Audio::Audio(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
|
||||
Audio::Audio(Concurrency::TaskQueue<false> &task_queue) : task_queue_(task_queue) {}
|
||||
|
||||
// MARK: - Inputs
|
||||
|
||||
@ -35,7 +35,7 @@ void Audio::set_volume(int volume) {
|
||||
posted_volume_ = volume;
|
||||
|
||||
// Post the volume change as a deferred event.
|
||||
task_queue_.defer([this, volume] () {
|
||||
task_queue_.enqueue([this, volume] () {
|
||||
volume_ = volume;
|
||||
set_volume_multiplier();
|
||||
});
|
||||
@ -47,7 +47,7 @@ void Audio::set_enabled(bool on) {
|
||||
posted_enable_mask_ = int(on);
|
||||
|
||||
// Post the enabled mask change as a deferred event.
|
||||
task_queue_.defer([this, on] () {
|
||||
task_queue_.enqueue([this, on] () {
|
||||
enabled_mask_ = int(on);
|
||||
set_volume_multiplier();
|
||||
});
|
||||
|
@ -27,7 +27,7 @@ namespace Macintosh {
|
||||
*/
|
||||
class Audio: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
Audio(Concurrency::DeferringAsyncTaskQueue &task_queue);
|
||||
Audio(Concurrency::TaskQueue<false> &task_queue);
|
||||
|
||||
/*!
|
||||
Macintosh audio is (partly) sourced by the same scanning
|
||||
@ -58,7 +58,7 @@ class Audio: public ::Outputs::Speaker::SampleSource {
|
||||
constexpr static bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
Concurrency::TaskQueue<false> &task_queue_;
|
||||
|
||||
// A queue of fetched samples; read from by one thread,
|
||||
// written to by another.
|
||||
|
@ -16,7 +16,7 @@ namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
struct DeferredAudio {
|
||||
Concurrency::DeferringAsyncTaskQueue queue;
|
||||
Concurrency::TaskQueue<false> queue;
|
||||
Audio audio;
|
||||
Outputs::Speaker::PullLowpass<Audio> speaker;
|
||||
HalfCycles time_since_update;
|
||||
|
@ -39,7 +39,7 @@ class Bus {
|
||||
PIA mos6532_;
|
||||
TIA tia_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
TIASound tia_sound_;
|
||||
Outputs::Speaker::PullLowpass<TIASound> speaker_;
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
using namespace Atari2600;
|
||||
|
||||
Atari2600::TIASound::TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
Atari2600::TIASound::TIASound(Concurrency::TaskQueue<false> &audio_queue) :
|
||||
audio_queue_(audio_queue),
|
||||
poly4_counter_{0x00f, 0x00f},
|
||||
poly5_counter_{0x01f, 0x01f},
|
||||
@ -18,20 +18,20 @@ Atari2600::TIASound::TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue)
|
||||
{}
|
||||
|
||||
void Atari2600::TIASound::set_volume(int channel, uint8_t volume) {
|
||||
audio_queue_.defer([target = &volume_[channel], volume]() {
|
||||
audio_queue_.enqueue([target = &volume_[channel], volume]() {
|
||||
*target = volume & 0xf;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::TIASound::set_divider(int channel, uint8_t divider) {
|
||||
audio_queue_.defer([this, channel, divider]() {
|
||||
audio_queue_.enqueue([this, channel, divider]() {
|
||||
divider_[channel] = divider & 0x1f;
|
||||
divider_counter_[channel] = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::TIASound::set_control(int channel, uint8_t control) {
|
||||
audio_queue_.defer([target = &control_[channel], control]() {
|
||||
audio_queue_.enqueue([target = &control_[channel], control]() {
|
||||
*target = control & 0xf;
|
||||
});
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ constexpr int CPUTicksPerAudioTick = 2;
|
||||
|
||||
class TIASound: public Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
TIASound(Concurrency::TaskQueue<false> &audio_queue);
|
||||
|
||||
void set_volume(int channel, uint8_t volume);
|
||||
void set_divider(int channel, uint8_t divider);
|
||||
@ -32,7 +32,7 @@ class TIASound: public Outputs::Speaker::SampleSource {
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
Concurrency::TaskQueue<false> &audio_queue_;
|
||||
|
||||
uint8_t volume_[2];
|
||||
uint8_t divider_[2];
|
||||
|
@ -485,7 +485,7 @@ class ConcreteMachine:
|
||||
JustInTimeActor<Motorola::ACIA::ACIA, HalfCycles, 16> keyboard_acia_;
|
||||
JustInTimeActor<Motorola::ACIA::ACIA, HalfCycles, 16> midi_acia_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
GI::AY38910::AY38910<false> ay_;
|
||||
Outputs::Speaker::PullLowpass<GI::AY38910::AY38910<false>> speaker_;
|
||||
HalfCycles cycles_since_audio_update_;
|
||||
|
@ -381,7 +381,7 @@ class ConcreteMachine:
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
JustInTimeActor<TI::TMS::TMS9918> vdp_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
TI::SN76489 sn76489_;
|
||||
GI::AY38910::AY38910<false> ay_;
|
||||
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>> mixer_;
|
||||
|
@ -770,7 +770,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
|
||||
// Outputs
|
||||
JustInTimeActor<VideoOutput, Cycles> video_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
SoundGenerator sound_generator_;
|
||||
Outputs::Speaker::PullLowpass<SoundGenerator> speaker_;
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
SoundGenerator::SoundGenerator(Concurrency::TaskQueue<false> &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void SoundGenerator::set_sample_volume_range(std::int16_t range) {
|
||||
@ -36,13 +36,13 @@ void SoundGenerator::skip_samples(std::size_t number_of_samples) {
|
||||
}
|
||||
|
||||
void SoundGenerator::set_divider(uint8_t divider) {
|
||||
audio_queue_.defer([this, divider]() {
|
||||
audio_queue_.enqueue([this, divider]() {
|
||||
divider_ = divider * 32 / clock_rate_divider;
|
||||
});
|
||||
}
|
||||
|
||||
void SoundGenerator::set_is_enabled(bool is_enabled) {
|
||||
audio_queue_.defer([this, is_enabled]() {
|
||||
audio_queue_.enqueue([this, is_enabled]() {
|
||||
is_enabled_ = is_enabled;
|
||||
counter_ = 0;
|
||||
});
|
||||
|
@ -16,7 +16,7 @@ namespace Electron {
|
||||
|
||||
class SoundGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
SoundGenerator(Concurrency::TaskQueue<false> &audio_queue);
|
||||
|
||||
void set_divider(uint8_t divider);
|
||||
|
||||
@ -31,7 +31,7 @@ class SoundGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
Concurrency::TaskQueue<false> &audio_queue_;
|
||||
unsigned int counter_ = 0;
|
||||
unsigned int divider_ = 0;
|
||||
bool is_enabled_ = false;
|
||||
|
@ -12,12 +12,12 @@ using namespace Enterprise::Dave;
|
||||
|
||||
// MARK: - Audio generator
|
||||
|
||||
Audio::Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
Audio::Audio(Concurrency::TaskQueue<false> &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void Audio::write(uint16_t address, uint8_t value) {
|
||||
address &= 0x1f;
|
||||
audio_queue_.defer([address, value, this] {
|
||||
audio_queue_.enqueue([address, value, this] {
|
||||
switch(address) {
|
||||
case 0: case 2: case 4:
|
||||
channels_[address >> 1].reload = (channels_[address >> 1].reload & 0xff00) | value;
|
||||
@ -63,7 +63,7 @@ void Audio::write(uint16_t address, uint8_t value) {
|
||||
}
|
||||
|
||||
void Audio::set_sample_volume_range(int16_t range) {
|
||||
audio_queue_.defer([range, this] {
|
||||
audio_queue_.enqueue([range, this] {
|
||||
volume_ = range / (63*4);
|
||||
});
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ enum class Interrupt: uint8_t {
|
||||
*/
|
||||
class Audio: public Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
Audio(Concurrency::TaskQueue<false> &audio_queue);
|
||||
|
||||
/// Modifies an register in the audio range; only the low 4 bits are
|
||||
/// used for register decoding so it's assumed that the caller has
|
||||
@ -43,7 +43,7 @@ class Audio: public Outputs::Speaker::SampleSource {
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
Concurrency::TaskQueue<false> &audio_queue_;
|
||||
|
||||
// Global divider (i.e. 8MHz/12Mhz switch).
|
||||
uint8_t global_divider_;
|
||||
|
@ -705,7 +705,7 @@ template <bool has_disk_controller, bool is_6mhz> class ConcreteMachine:
|
||||
bool previous_nick_interrupt_line_ = false;
|
||||
// Cf. timing guesses above.
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
Dave::Audio dave_audio_;
|
||||
Outputs::Speaker::PullLowpass<Dave::Audio> speaker_;
|
||||
HalfCycles time_since_audio_update_;
|
||||
|
@ -747,7 +747,7 @@ class ConcreteMachine:
|
||||
JustInTimeActor<TI::TMS::TMS9918> vdp_;
|
||||
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
GI::AY38910::AY38910<false> ay_;
|
||||
Audio::Toggle audio_toggle_;
|
||||
Konami::SCC scc_;
|
||||
|
@ -459,7 +459,7 @@ class ConcreteMachine:
|
||||
// This is as per the audio control register;
|
||||
// see https://www.smspower.org/Development/AudioControlPort
|
||||
update_audio();
|
||||
audio_queue_.defer([this, mode] {
|
||||
audio_queue_.enqueue([this, mode] {
|
||||
switch(mode & 3) {
|
||||
case 0: // SN76489 only; the default.
|
||||
mixer_.set_relative_volumes({1.0f, 0.0f});
|
||||
@ -487,7 +487,7 @@ class ConcreteMachine:
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
JustInTimeActor<TI::TMS::TMS9918> vdp_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
TI::SN76489 sn76489_;
|
||||
Yamaha::OPL::OPLL opll_;
|
||||
Outputs::Speaker::CompoundSource<decltype(sn76489_), decltype(opll_)> mixer_;
|
||||
|
@ -178,7 +178,7 @@ class TapePlayer: public Storage::Tape::BinaryTapePlayer {
|
||||
*/
|
||||
class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
VIAPortHandler(Concurrency::DeferringAsyncTaskQueue &audio_queue, AY &ay8910, Speaker &speaker, TapePlayer &tape_player, Keyboard &keyboard) :
|
||||
VIAPortHandler(Concurrency::TaskQueue<false> &audio_queue, AY &ay8910, Speaker &speaker, TapePlayer &tape_player, Keyboard &keyboard) :
|
||||
audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard)
|
||||
{
|
||||
// Attach a couple of joysticks.
|
||||
@ -254,7 +254,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
uint8_t porta_output_ = 0xff;
|
||||
HalfCycles cycles_since_ay_update_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
Concurrency::TaskQueue<false> &audio_queue_;
|
||||
AY &ay8910_;
|
||||
Speaker &speaker_;
|
||||
TapePlayer &tape_player_;
|
||||
@ -711,7 +711,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
|
||||
// Outputs
|
||||
JustInTimeActor<VideoOutput, Cycles> video_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
GI::AY38910::AY38910<false> ay8910_;
|
||||
Speaker speaker_;
|
||||
|
||||
|
@ -468,7 +468,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Audio
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
using AY = GI::AY38910::AY38910<false>;
|
||||
AY ay_;
|
||||
Outputs::Speaker::PullLowpass<AY> speaker_;
|
||||
|
@ -849,7 +849,7 @@ template<Model model> class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Audio.
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::TaskQueue<false> audio_queue_;
|
||||
GI::AY38910::AY38910<false> ay_;
|
||||
Audio::Toggle audio_toggle_;
|
||||
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910<false>, Audio::Toggle> mixer_;
|
||||
|
@ -33,7 +33,6 @@
|
||||
4B05401F219D1618001BF69C /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B05401D219D1618001BF69C /* ScanTarget.cpp */; };
|
||||
4B055A7A1FAE78A00060FFFF /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055A771FAE78210060FFFF /* SDL2.framework */; };
|
||||
4B055A7E1FAE84AA0060FFFF /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B055A7C1FAE84A50060FFFF /* main.cpp */; };
|
||||
4B055A8D1FAE85920060FFFF /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; };
|
||||
4B055A8F1FAE85A90060FFFF /* FileHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */; };
|
||||
4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; };
|
||||
4B055A911FAE85B50060FFFF /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
|
||||
@ -221,7 +220,6 @@
|
||||
4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B322E031F5A2E3C004EB04C /* Z80Base.cpp */; };
|
||||
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */; };
|
||||
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; };
|
||||
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; };
|
||||
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; };
|
||||
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */; };
|
||||
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */; };
|
||||
@ -348,7 +346,6 @@
|
||||
4B7752C128217F490073E2C5 /* FAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B477709268FBE4D005C2340 /* FAT.cpp */; };
|
||||
4B7752C228217F5C0073E2C5 /* Spectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */; };
|
||||
4B7752C328217F720073E2C5 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD39526360DDF00B3C866 /* Z80.cpp */; };
|
||||
4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */; };
|
||||
4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; };
|
||||
4B778EF123A5D6B50000D260 /* 9918.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E04F91FC9FA3100F43484 /* 9918.cpp */; };
|
||||
4B778EF323A5DB230000D260 /* PCMSegment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518731F75E91800926311 /* PCMSegment.cpp */; };
|
||||
@ -1298,7 +1295,6 @@
|
||||
4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; };
|
||||
4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AmstradCPC.cpp; sourceTree = "<group>"; };
|
||||
4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AmstradCPC.hpp; sourceTree = "<group>"; };
|
||||
4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AsyncTaskQueue.cpp; sourceTree = "<group>"; };
|
||||
4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AsyncTaskQueue.hpp; sourceTree = "<group>"; };
|
||||
4B3AF7D02413470E00873C0B /* Enum.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Enum.hpp; sourceTree = "<group>"; };
|
||||
4B3AF7D12413472200873C0B /* Struct.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Struct.hpp; sourceTree = "<group>"; };
|
||||
@ -2138,7 +2134,6 @@
|
||||
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; };
|
||||
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
|
||||
4BE0151C286A8C8E00EA42E9 /* MemorySwitches.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemorySwitches.hpp; sourceTree = "<group>"; };
|
||||
4BE0151E28766ECF00EA42E9 /* AsyncUpdater.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AsyncUpdater.hpp; sourceTree = "<group>"; };
|
||||
4BE0A3EC237BB170002AB46F /* ST.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ST.cpp; sourceTree = "<group>"; };
|
||||
4BE0A3ED237BB170002AB46F /* ST.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ST.hpp; sourceTree = "<group>"; };
|
||||
4BE211DD253E4E4800435408 /* 65C02_no_Rockwell_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_no_Rockwell_test.bin; path = "Klaus Dormann/65C02_no_Rockwell_test.bin"; sourceTree = "<group>"; };
|
||||
@ -2753,9 +2748,7 @@
|
||||
4B3940E81DA83C8700427841 /* Concurrency */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */,
|
||||
4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */,
|
||||
4BE0151E28766ECF00EA42E9 /* AsyncUpdater.hpp */,
|
||||
);
|
||||
name = Concurrency;
|
||||
path = ../../Concurrency;
|
||||
@ -5637,7 +5630,6 @@
|
||||
4B0ACC2923775819008902D0 /* DMAController.cpp in Sources */,
|
||||
4B055A951FAE85BB0060FFFF /* BitReverse.cpp in Sources */,
|
||||
4B055ACE1FAE9B030060FFFF /* Plus3.cpp in Sources */,
|
||||
4B055A8D1FAE85920060FFFF /* AsyncTaskQueue.cpp in Sources */,
|
||||
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */,
|
||||
4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B8DF506254E3C9D00F3433C /* ADB.cpp in Sources */,
|
||||
@ -5887,7 +5879,6 @@
|
||||
4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
|
||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
|
||||
4B051CA826781D6500CA44E8 /* StaticAnalyser.cpp in Sources */,
|
||||
4B3940E71DA83C8300427841 /* AsyncTaskQueue.cpp in Sources */,
|
||||
4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */,
|
||||
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
|
||||
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */,
|
||||
@ -6118,7 +6109,6 @@
|
||||
4B7752A728217E060073E2C5 /* Blitter.cpp in Sources */,
|
||||
4B778F4F23A5F21C0000D260 /* StaticAnalyser.cpp in Sources */,
|
||||
4B8DD3682633B2D400B3C866 /* SpectrumVideoContentionTests.mm in Sources */,
|
||||
4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */,
|
||||
4B7752A928217E200073E2C5 /* 65816Storage.cpp in Sources */,
|
||||
4B7752AC28217E6E0073E2C5 /* StaticAnalyser.cpp in Sources */,
|
||||
4B778F1223A5EC720000D260 /* CRT.cpp in Sources */,
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
#include "../../../../ClockReceiver/TimeTypes.hpp"
|
||||
#include "../../../../ClockReceiver/ScanSynchroniser.hpp"
|
||||
#include "../../../../Concurrency/AsyncUpdater.hpp"
|
||||
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
#import "CSStaticAnalyser+TargetVector.h"
|
||||
#import "NSBundle+DataResource.h"
|
||||
@ -122,7 +122,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
CSJoystickManager *_joystickManager;
|
||||
NSMutableArray<CSMachineLED *> *_leds;
|
||||
|
||||
Concurrency::AsyncUpdater<MachineUpdater> updater;
|
||||
Concurrency::TaskQueue<true, MachineUpdater> updater;
|
||||
Time::ScanSynchroniser _scanSynchroniser;
|
||||
|
||||
NSTimer *_joystickTimer;
|
||||
@ -455,7 +455,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
|
||||
- (void)applyInputEvent:(dispatch_block_t)event {
|
||||
updater.update([event] {
|
||||
updater.enqueue([event] {
|
||||
event();
|
||||
});
|
||||
}
|
||||
@ -669,13 +669,13 @@ struct ActivityObserver: public Activity::Observer {
|
||||
#pragma mark - Timer
|
||||
|
||||
- (void)audioQueueIsRunningDry:(nonnull CSAudioQueue *)audioQueue {
|
||||
updater.update([self] {
|
||||
updater.enqueue([self] {
|
||||
updater.performer.machine->flush_output(MachineTypes::TimedMachine::Output::Audio);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)scanTargetViewDisplayLinkDidFire:(CSScanTargetView *)view now:(const CVTimeStamp *)now outputTime:(const CVTimeStamp *)outputTime {
|
||||
updater.update([self] {
|
||||
updater.enqueue([self] {
|
||||
// Grab a pointer to the timed machine from somewhere where it has already
|
||||
// been dynamically cast, to avoid that cost here.
|
||||
MachineTypes::TimedMachine *const timed_machine = updater.performer.machine;
|
||||
|
@ -367,12 +367,12 @@ template <typename SampleSource> class PullLowpass: public LowpassBase<PullLowpa
|
||||
The speaker will advance by obtaining data from the sample source supplied
|
||||
at construction, filtering it and passing it on to the speaker's delegate if there is one.
|
||||
*/
|
||||
void run_for(Concurrency::DeferringAsyncTaskQueue &queue, const Cycles cycles) {
|
||||
void run_for(Concurrency::TaskQueue<false> &queue, const Cycles cycles) {
|
||||
if(cycles == Cycles(0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
queue.defer([this, cycles] {
|
||||
queue.enqueue([this, cycles] {
|
||||
run_for(cycles);
|
||||
});
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ class DiskImageHolderBase: public Disk {
|
||||
protected:
|
||||
std::set<Track::Address> unwritten_tracks_;
|
||||
std::map<Track::Address, std::shared_ptr<Track>> cached_tracks_;
|
||||
std::unique_ptr<Concurrency::AsyncTaskQueue> update_queue_;
|
||||
std::unique_ptr<Concurrency::TaskQueue<true>> update_queue_;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -20,7 +20,7 @@ template <typename T> bool DiskImageHolder<T>::get_is_read_only() {
|
||||
|
||||
template <typename T> void DiskImageHolder<T>::flush_tracks() {
|
||||
if(!unwritten_tracks_.empty()) {
|
||||
if(!update_queue_) update_queue_ = std::make_unique<Concurrency::AsyncTaskQueue>();
|
||||
if(!update_queue_) update_queue_ = std::make_unique<Concurrency::TaskQueue<true>>();
|
||||
|
||||
using TrackMap = std::map<Track::Address, std::shared_ptr<Track>>;
|
||||
std::shared_ptr<TrackMap> track_copies(new TrackMap);
|
||||
|
Loading…
x
Reference in New Issue
Block a user