C++ standard only interrupts implementation

This commit is contained in:
Robert Bendun 2023-02-22 19:34:24 +01:00
parent e26474a2d7
commit 2d61896824
4 changed files with 67 additions and 42 deletions

View File

@ -430,7 +430,9 @@ static Result<Value> builtin_par(Interpreter &interpreter, std::vector<Value> ar
for (auto const& note : chord->notes) { for (auto const& note : chord->notes) {
if (note.base) { if (note.base) {
interpreter.current_context->port->send_note_on(0, *note.into_midi_note(), 127); auto const n = *note.into_midi_note();
interpreter.current_context->port->send_note_on(0, n, 127);
interpreter.active_notes.insert({ 0, n });
} }
} }
@ -438,7 +440,9 @@ static Result<Value> builtin_par(Interpreter &interpreter, std::vector<Value> ar
for (auto const& note : chord->notes) { for (auto const& note : chord->notes) {
if (note.base) { if (note.base) {
interpreter.current_context->port->send_note_off(0, *note.into_midi_note(), 127); auto const n = *note.into_midi_note();
interpreter.current_context->port->send_note_off(0, n, 127);
interpreter.active_notes.erase({ 0, n });
} }
} }
return result; return result;
@ -548,12 +552,16 @@ static Result<Value> builtin_sim(Interpreter &interpreter, std::vector<Value> ar
for (auto const& instruction : schedule) { for (auto const& instruction : schedule) {
auto const dur = ctx.length_to_duration({instruction.when}); auto const dur = ctx.length_to_duration({instruction.when});
if (start_time < dur) { if (start_time < dur) {
std::this_thread::sleep_for(dur - start_time); interpreter.sleep(dur - start_time);
start_time = dur; start_time = dur;
} }
switch (instruction.action) { switch (instruction.action) {
break; case Instruction::On: interpreter.current_context->port->send_note_on(0, instruction.note, 127); break; case Instruction::On:
break; case Instruction::Off: interpreter.current_context->port->send_note_off(0, instruction.note, 127); interpreter.current_context->port->send_note_on(0, instruction.note, 127);
interpreter.active_notes.insert({ 0, instruction.note });
break; case Instruction::Off:
interpreter.current_context->port->send_note_off(0, instruction.note, 127);
interpreter.active_notes.erase({ 0, instruction.note });
} }
} }

View File

@ -6,6 +6,8 @@
#include <iostream> #include <iostream>
#include <random> #include <random>
#include <thread> #include <thread>
#include <condition_variable>
#include <mutex>
std::unordered_map<std::string, Intrinsic> Interpreter::operators {}; std::unordered_map<std::string, Intrinsic> Interpreter::operators {};
@ -53,6 +55,8 @@ Interpreter::~Interpreter()
Result<Value> Interpreter::eval(Ast &&ast) Result<Value> Interpreter::eval(Ast &&ast)
{ {
handle_potential_interrupt();
switch (ast.type) { switch (ast.type) {
case Ast::Type::Literal: case Ast::Type::Literal:
switch (ast.token.type) { switch (ast.token.type) {
@ -248,8 +252,10 @@ std::optional<Error> Interpreter::play(Chord chord)
Try(ensure_midi_connection_available(*this, "play")); Try(ensure_midi_connection_available(*this, "play"));
auto &ctx = *current_context; auto &ctx = *current_context;
handle_potential_interrupt();
if (chord.notes.size() == 0) { if (chord.notes.size() == 0) {
std::this_thread::sleep_for(ctx.length_to_duration(ctx.length)); sleep(ctx.length_to_duration(ctx.length));
return {}; return {};
} }
@ -273,7 +279,7 @@ std::optional<Error> Interpreter::play(Chord chord)
for (auto const& note : chord.notes) { for (auto const& note : chord.notes) {
if (max_time != Number(0)) { if (max_time != Number(0)) {
max_time -= *note.length; max_time -= *note.length;
std::this_thread::sleep_for(ctx.length_to_duration(*note.length)); sleep(ctx.length_to_duration(*note.length));
} }
if (note.base) { if (note.base) {
current_context->port->send_note_off(0, *note.into_midi_note(), 127); current_context->port->send_note_off(0, *note.into_midi_note(), 127);
@ -442,3 +448,31 @@ void Interpreter::snapshot(std::ostream& out)
} }
out << std::flush; out << std::flush;
} }
// TODO This only supports single-threaded interpreter execution
static std::atomic<bool> interrupted = false;
static std::condition_variable condvar;
static std::mutex mu;
void Interpreter::handle_potential_interrupt()
{
if (interrupted) {
interrupted = false;
throw KeyboardInterrupt{};
}
}
void Interpreter::issue_interrupt()
{
interrupted = true;
condvar.notify_all();
}
void Interpreter::sleep(std::chrono::duration<float> time)
{
if (std::unique_lock lock(mu); condvar.wait_for(lock, time) == std::cv_status::no_timeout) {
ensure(interrupted, "Only interruption can result in quiting conditional variable without timeout");
interrupted = false;
throw KeyboardInterrupt{};
}
}

View File

@ -8,6 +8,12 @@
#include <unordered_map> #include <unordered_map>
#include <set> #include <set>
struct KeyboardInterrupt : std::exception
{
~KeyboardInterrupt() = default;
char const* what() const noexcept override { return "KeyboardInterrupt"; }
};
/// Given program tree evaluates it into Value /// Given program tree evaluates it into Value
struct Interpreter struct Interpreter
{ {
@ -25,7 +31,6 @@ struct Interpreter
std::multiset<std::pair<unsigned, unsigned>> active_notes; std::multiset<std::pair<unsigned, unsigned>> active_notes;
Starter starter; Starter starter;
Interpreter(); Interpreter();
@ -58,10 +63,19 @@ struct Interpreter
/// Dumps snapshot of interpreter into stream /// Dumps snapshot of interpreter into stream
void snapshot(std::ostream& out); void snapshot(std::ostream& out);
/// Turn all notes that have been played but don't finished playing
void turn_off_all_active_notes(); void turn_off_all_active_notes();
/// Handles interrupt if any occured
void handle_potential_interrupt();
/// Issue new interrupt
void issue_interrupt();
/// Sleep for at least given time or until interrupt
void sleep(std::chrono::duration<float>);
}; };
std::optional<Error> ensure_midi_connection_available(Interpreter&, std::string_view operation_name); std::optional<Error> ensure_midi_connection_available(Interpreter&, std::string_view operation_name);
#endif #endif

View File

@ -38,12 +38,6 @@ static unsigned repl_line_number = 1;
#define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0) #define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0)
struct KeyboardInterrupt : std::exception
{
~KeyboardInterrupt() = default;
char const* what() const noexcept override { return "KeyboardInterrupt"; }
};
/// Pop string from front of an array /// Pop string from front of an array
template<typename T = std::string_view> template<typename T = std::string_view>
@ -437,33 +431,8 @@ static std::optional<Error> Main(std::span<char const*> args)
std::exit(1); std::exit(1);
} }
{ static Runner runner;
static auto thread = pthread_self(); std::signal(SIGINT, [](int sig) { if (sig == SIGINT) runner.interpreter.issue_interrupt(); });
static auto thread_id = std::this_thread::get_id();
// TODO IS this a good solution?
static auto *handler = +[](int sig, siginfo_t*, void*) {
struct sigaction sa;
sigaction(SIGINT, nullptr, &sa);
pthread_sigmask(SIG_UNBLOCK, &sa.sa_mask, nullptr);
if (thread_id == std::this_thread::get_id()) {
if (sig == SIGINT) {
throw KeyboardInterrupt{};
}
} else {
pthread_kill(thread, SIGINT);
}
};
struct sigaction sa = {};
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGINT);
sa.sa_sigaction = handler;
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
}
Runner runner;
for (auto const& [type, argument] : runnables) { for (auto const& [type, argument] : runnables) {
if (type == Run::Argument) { if (type == Run::Argument) {