Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perf/rhythm seeking #28

Merged
merged 9 commits into from
Jul 6, 2024
10 changes: 5 additions & 5 deletions benches/benchmarks/rhythm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ pub fn run(c: &mut Criterion) {
pub fn seek(c: &mut Criterion) {
let mut group = c.benchmark_group("Rust Phrase");
let phrase = create_phrase();
let samples_per_sec = phrase.time_base().samples_per_sec;
let samples_per_sec = phrase.time_base().samples_per_sec as SampleTime;
let seek_step = 10;
let seek_time = 60 * 60;
group.bench_function("Seek", |b| {
Expand All @@ -202,10 +202,10 @@ pub fn seek(c: &mut Criterion) {
phrase
},
|mut phrase| {
let mut sample_time = samples_per_sec as SampleTime;
while sample_time < (seek_time * samples_per_sec) as SampleTime {
phrase.seek_until_time(sample_time);
sample_time += seek_step * samples_per_sec as SampleTime;
let mut sample_time = samples_per_sec;
while sample_time < seek_time * samples_per_sec {
phrase.advance_until_time(sample_time);
sample_time += seek_step * samples_per_sec;
}
},
criterion::BatchSize::SmallInput,
Expand Down
10 changes: 5 additions & 5 deletions benches/benchmarks/scripted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ pub fn run(c: &mut Criterion) {
pub fn seek(c: &mut Criterion) {
let mut group = c.benchmark_group("Scripted Phrase");
let phrase = create_phrase();
let samples_per_sec = phrase.time_base().samples_per_sec;
let samples_per_sec = phrase.time_base().samples_per_sec as SampleTime;
let seek_step = 10;
let seek_time = 60 * 60;
group.bench_function("Seek", |b| {
Expand All @@ -191,10 +191,10 @@ pub fn seek(c: &mut Criterion) {
phrase
},
|mut phrase| {
let mut sample_time = samples_per_sec as SampleTime;
while sample_time < (seek_time * samples_per_sec) as SampleTime {
phrase.seek_until_time(sample_time);
sample_time += seek_step * samples_per_sec as SampleTime;
let mut sample_time = samples_per_sec;
while sample_time < seek_time * samples_per_sec {
phrase.advance_until_time(sample_time);
sample_time += seek_step * samples_per_sec;
}
},
criterion::BatchSize::SmallInput,
Expand Down
124 changes: 91 additions & 33 deletions src/bindings/callback.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{borrow::Cow, fmt::Debug};
use std::{borrow::Cow, collections::HashMap, fmt::Debug};

use mlua::prelude::*;

Expand Down Expand Up @@ -71,18 +71,18 @@ pub fn add_lua_callback_error(name: &str, err: &LuaError) {
/// with values before it's called.
///
/// Errors from callbacks should be handled by calling `self.handle_error` so external clients
/// can deal with them later, as apropriate.
/// can deal with them later, as appropriate.
///
/// By memorizing the original generator function and environment, it also can be reset to its
/// initial state by calling the original generator function again to fetch a new freshly
/// initialized function.
///
/// TODO: Upvalues of generators or simple functions could actuially be collected and restored
/// TODO: Upvalues of generators or simple functions could actually be collected and restored
/// too, but this uses debug functionality and may break some upvalues.
#[derive(Debug, Clone)]
pub(crate) struct LuaCallback {
environment: Option<LuaOwnedTable>,
context: LuaOwnedTable,
context: LuaOwnedAnyUserData,
generator: Option<LuaOwnedFunction>,
function: LuaOwnedFunction,
initialized: bool,
Expand All @@ -96,8 +96,15 @@ impl LuaCallback {

/// Create a new Callback from an owned lua function.
pub fn with_owned(lua: &Lua, function: LuaOwnedFunction) -> LuaResult<Self> {
// create an empty context and memorize the function without calling it
let context = lua.create_table()?.into_owned();
// create and register the callback context
LuaCallbackContext::register(lua)?;
let context = lua
.create_any_userdata(LuaCallbackContext {
values: HashMap::new(),
external_values: HashMap::new(),
})?
.into_owned();
// and memorize the function without calling it
let environment = function.to_ref().environment().map(LuaTable::into_owned);
let generator = None;
let initialized = false;
Expand All @@ -112,27 +119,31 @@ impl LuaCallback {

/// Sets the emitter time base context for the callback.
pub fn set_context_time_base(&mut self, time_base: &BeatTimeBase) -> LuaResult<()> {
let table = self.context.to_ref();
table.raw_set("beats_per_min", time_base.beats_per_min)?;
table.raw_set("beats_per_bar", time_base.beats_per_bar)?;
table.raw_set("samples_per_sec", time_base.samples_per_sec)?;
let values = &mut self.context.borrow_mut::<LuaCallbackContext>()?.values;
values.insert(b"beats_per_min", time_base.beats_per_min as LuaNumber);
values.insert(b"beats_per_min", time_base.beats_per_min as LuaNumber);
values.insert(b"beats_per_bar", time_base.beats_per_bar as LuaNumber);
values.insert(b"samples_per_sec", time_base.samples_per_sec as LuaNumber);
Ok(())
}

/// Sets external emitter context for the callback.
pub fn set_context_external_data(&mut self, data: &[(Cow<str>, f64)]) -> LuaResult<()> {
let table = self.context.to_ref();
let external_values = &mut self
.context
.borrow_mut::<LuaCallbackContext>()?
.external_values;
for (key, value) in data {
table.raw_set(key as &str, *value)?;
external_values.insert(key.to_string(), *value as LuaNumber);
}
Ok(())
}

/// Sets the pulse value emitter context for the callback.
pub fn set_context_pulse_value(&mut self, pulse: PulseIterItem) -> LuaResult<()> {
let table = self.context.to_ref();
table.raw_set("pulse_value", pulse.value)?;
table.raw_set("pulse_time", pulse.step_time)?;
let values = &mut self.context.borrow_mut::<LuaCallbackContext>()?.values;
values.insert(b"pulse_value", pulse.value as LuaNumber);
values.insert(b"pulse_time", pulse.step_time as LuaNumber);
Ok(())
}

Expand All @@ -142,16 +153,16 @@ impl LuaCallback {
pulse_step: usize,
pulse_time_step: f64,
) -> LuaResult<()> {
let table = self.context.to_ref();
table.raw_set("pulse_step", pulse_step + 1)?;
table.raw_set("pulse_time_step", pulse_time_step)?;
let values = &mut self.context.borrow_mut::<LuaCallbackContext>()?.values;
values.insert(b"pulse_step", (pulse_step + 1) as LuaNumber);
values.insert(b"pulse_time_step", pulse_time_step as LuaNumber);
Ok(())
}

/// Sets the step emitter context for the callback.
pub fn set_context_step(&mut self, step: usize) -> LuaResult<()> {
let table = self.context.to_ref();
table.raw_set("step", step + 1)?;
let values = &mut self.context.borrow_mut::<LuaCallbackContext>()?.values;
values.insert(b"step", (step + 1) as LuaNumber);
Ok(())
}

Expand All @@ -162,10 +173,10 @@ impl LuaCallback {
step: usize,
step_length: f64,
) -> LuaResult<()> {
let table = self.context.to_ref();
table.raw_set("channel", channel + 1)?;
table.raw_set("step", step + 1)?;
table.raw_set("step_length", step_length)?;
let values = &mut self.context.borrow_mut::<LuaCallbackContext>()?.values;
values.insert(b"channel", (channel + 1) as LuaNumber);
values.insert(b"step", (step + 1) as LuaNumber);
values.insert(b"step_length", step_length as LuaNumber);
Ok(())
}

Expand Down Expand Up @@ -221,34 +232,45 @@ impl LuaCallback {
Ok(())
}

/// Name of the inner function for errors. Usually will be an annonymous function.
/// Name of the inner function for errors. Usually will be an anonymous function.
pub fn name(&self) -> String {
self.function
.to_ref()
.info()
.name
.unwrap_or("annonymous function".to_string())
.unwrap_or("anonymous function".to_string())
}

/// Invoke the Lua function callback or generator.
/// Returns true if the callback is a generator.
///
/// To test this, the callback must have run at least once, so it returns None if it never has.
pub fn is_stateful(&self) -> Option<bool> {
if self.initialized {
Some(self.generator.is_some())
} else {
None
}
}

/// Invoke the Lua function or generator and return its result as LuaValue.
pub fn call(&mut self) -> LuaResult<LuaValue> {
self.call_with_arg(LuaValue::Nil)
}

/// Invoke the Lua function callback or generator with an additional argument.
/// Invoke the Lua function or generator with an additional argument and return its result as LuaValue.
pub fn call_with_arg<'lua, A: IntoLua<'lua> + Clone>(
&'lua mut self,
arg: A,
) -> LuaResult<LuaValue<'lua>> {
if self.initialized {
self.function.call((self.context.to_ref(), arg))
self.function.call((&self.context, arg))
} else {
self.initialized = true;
let result = {
// HACK: don't borrow self here, so we can borrow mut again to assign the generator function
// see https://stackoverflow.com/questions/73641155/how-to-force-rust-to-drop-a-mutable-borrow
let function = unsafe { &*(&self.function as *const LuaOwnedFunction) };
function.call::<_, LuaValue>((self.context.to_ref(), arg.clone()))?
function.call::<_, LuaValue>((&self.context, arg.clone()))?
};
if let Some(inner_function) = result.as_function().cloned().map(|f| f.into_owned()) {
// function returned a function -> is a generator. use the inner function instead.
Expand All @@ -259,8 +281,7 @@ impl LuaCallback {
.map(LuaTable::into_owned);
self.environment = environment;
self.generator = Some(std::mem::replace(&mut self.function, inner_function));
self.function
.call::<_, LuaValue>((self.context.to_ref(), arg))
self.function.call::<_, LuaValue>((&self.context, arg))
} else {
// function returned some value. use this function directly.
self.environment = None;
Expand Down Expand Up @@ -288,7 +309,7 @@ impl LuaCallback {
// then fetch a new fresh function from the generator
let value = function_generator
.to_ref()
.call::<_, LuaValue>(self.context.to_ref())?;
.call::<_, LuaValue>(&self.context)?;
if let Some(function) = value.as_function() {
self.function = function.clone().into_owned();
} else {
Expand All @@ -305,6 +326,43 @@ impl LuaCallback {
}
}

// -------------------------------------------------------------------------------------------------

/// Memorizes an optional set of values that are passed along as context with the callback.
#[derive(Debug, Clone)]
struct LuaCallbackContext {
values: HashMap<&'static [u8], LuaNumber>,
external_values: HashMap<String, LuaNumber>,
}

impl LuaCallbackContext {
fn register(lua: &Lua) -> LuaResult<()> {
// NB: registering for a specific engine is faster than implementing the UserData impl
// See https://github.com/mlua-rs/mlua/discussions/283
lua.register_userdata_type::<LuaCallbackContext>(|reg| {
reg.add_meta_field_with("__index", |lua| {
lua.create_function(
|lua, (this, key): (LuaUserDataRef<LuaCallbackContext>, LuaString)| {
if let Some(value) = this.values.get(key.as_bytes()) {
// fast path (string bytes)
value.into_lua(lua)
} else if let Some(value) =
this.external_values.get(key.to_string_lossy().as_ref())
{
// slower path (string )
value.into_lua(lua)
} else {
Err(mlua::Error::RuntimeError(
"no such field in context".to_string(),
))
}
},
)
})
})
}
}

// --------------------------------------------------------------------------------------------------

#[cfg(test)]
Expand Down
10 changes: 9 additions & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,15 @@ pub trait EventIter: Debug {
/// Returns an optional stack of event iter items, which should be emitted for the given pulse.
fn run(&mut self, pulse: PulseIterItem, emit_event: bool) -> Option<Vec<EventIterItem>>;

/// Create a new cloned instance of this event iter. This actualy is a clone(), wrapped into
/// Move iterator with the given pulse value forward without generating an event.
///
/// This can be used to optimize iterator skipping in some EventIter implementations, but by
/// default calls `run` and simply discards the generated event return value.
fn advance(&mut self, pulse: PulseIterItem, emit_event: bool) {
let _ = self.run(pulse, emit_event);
}

/// Create a new cloned instance of this event iter. This actually is a clone(), wrapped into
/// a `Box<dyn EventIter>`, but called 'duplicate' to avoid conflicts with possible
/// Clone impls.
fn duplicate(&self) -> Box<dyn EventIter>;
Expand Down
14 changes: 10 additions & 4 deletions src/event/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ impl CycleEventIter {
}

/// Generate a note event from a single cycle event, applying mappings if necessary
fn note_events(&mut self, event: CycleEvent) -> Result<Vec<Option<NoteEvent>>, String> {
fn map_note_event(&mut self, event: CycleEvent) -> Result<Vec<Option<NoteEvent>>, String> {
let mut note_events = {
if let Some(note_events) = self.mappings.get(event.string()) {
// apply custom note mappings
Expand All @@ -215,7 +215,7 @@ impl CycleEventIter {

/// Generate next batch of events from the next cycle run.
/// Converts cycle events to note events and flattens channels into note columns.
fn generate_events(&mut self) -> Vec<EventIterItem> {
fn generate(&mut self) -> Vec<EventIterItem> {
// run the cycle event generator
let events = {
match self.cycle.generate() {
Expand All @@ -232,7 +232,7 @@ impl CycleEventIter {
for event in channel_events.into_iter() {
let start = event.span().start();
let length = event.span().length();
match self.note_events(event) {
match self.map_note_event(event) {
Ok(note_events) => {
if !note_events.is_empty() {
timed_note_events.add(channel_index, start, length, note_events);
Expand Down Expand Up @@ -261,12 +261,18 @@ impl EventIter for CycleEventIter {

fn run(&mut self, _pulse: PulseIterItem, emit_event: bool) -> Option<Vec<EventIterItem>> {
if emit_event {
Some(self.generate_events())
Some(self.generate())
} else {
None
}
}

fn advance(&mut self, _pulse: PulseIterItem, emit_event: bool) {
if emit_event {
self.cycle.advance();
}
}

fn duplicate(&self) -> Box<dyn EventIter> {
Box::new(self.clone())
}
Expand Down
12 changes: 8 additions & 4 deletions src/event/fixed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,17 @@ impl EventIter for FixedEventIter {
return None;
}
let event = self.events[self.event_index].clone();
self.event_index += 1;
if self.event_index >= self.events.len() {
self.event_index = 0;
}
self.event_index = (self.event_index + 1) % self.events.len();
Some(vec![EventIterItem::new(event)])
}

fn advance(&mut self, _pulse: PulseIterItem, emit_event: bool) {
if !emit_event || self.events.is_empty() {
return;
}
self.event_index = (self.event_index + 1) % self.events.len();
}

fn duplicate(&self) -> Box<dyn EventIter> {
Box::new(self.clone())
}
Expand Down
16 changes: 6 additions & 10 deletions src/event/mutated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,13 @@ impl EventIter for MutatedEventIter {
}

fn run(&mut self, _pulse: PulseIterItem, emit_event: bool) -> Option<Vec<EventIterItem>> {
if emit_event {
let event = self.events[self.event_index].clone();
self.events[self.event_index] = Self::mutate(event.clone(), &mut self.map);
self.event_index += 1;
if self.event_index >= self.events.len() {
self.event_index = 0;
}
Some(vec![EventIterItem::new(event)])
} else {
None
if !emit_event || self.events.is_empty() {
return None;
}
let event = self.events[self.event_index].clone();
self.events[self.event_index] = Self::mutate(event.clone(), &mut self.map);
self.event_index = (self.event_index + 1) % self.events.len();
Some(vec![EventIterItem::new(event)])
}

fn duplicate(&self) -> Box<dyn EventIter> {
Expand Down
Loading
Loading