PistonDevelopers/dyon

call_ret without heap allocations?

obsoleszenz opened this issue · 3 comments

I'm currently evaluating if dyon is suitable to use as a scripting language in a soft realtime environment (audio dsp).
I'm building equis, a software dj mixer and i want it to have midi mappings, so that one can write a little script that maps incoming midi messages from a hardware controller to the neccessairy dsp parameter changes.

What I want to achieve is, that those scripts can be run in the audio realtime thread/callback, which is a soft realtime environment and therefore heap allocations should not happen.

I built a small PoC with dyon (https://codeberg.org/obsoleszenz/EQUIS/pulls/26) and was able to call a simple fn on_midi() ~ mut state method and assert_no_alloc didn't complain. But as soon as I'm trying to add a RustObject or Array variable as a parameter to the method, some allocations (maybe clones? maybe Box::new?) are happening. I looked a bit into the code and was the runtime.module is getting cloned and swapped.

So now my question, would it be possible to make dyons call() path so that it doesn't do/need heap allocations?

Btw thanks a lot for dyon, a borrowchecked/typed scripting language is amazing!

I also experimented with audio and added grab support at top level to allow pre-allocation of data such as arrays.

E.g.

fn main() {
    data := grab [1, 2, 3, 4, ...]
    ...
}

Is this a feature that you are looking for?

I haven't looked into the cloning of the runtime module. Do you have an idea of where in the Dyon codebase this is caused?

I also experimented with audio and added grab support at top level to allow pre-allocation of data such as arrays.

E.g.

fn main() {
    data := grab [1, 2, 3, 4, ...]
    ...
}

Is this a feature that you are looking for?

Didn't know about grab, that looks useful :)

I haven't looked into the cloning of the runtime module. Do you have an idea of where in the Dyon codebase this is caused?

I looked again and i think it should be fine as it's just an Arc that is getting cloned. I will have to look again into it. But I found another way, I'm now passing in the midi message as a vec4 which works well.

In fact I can now do this which is already quite helpful:

fn init_state() ~ mut state {
    state.counter := 0
    state.name := "Test"
}

fn on_midi(midi_in: vec4) ~ mut state {
    state.counter += 1

    status := x(midi_in)
    channel := y(midi_in)
    note := z(midi_in)
    value := w(midi_in)
    value_normalized := value / 127.0

    if note == 1 {
        dsp_set(ID_VOLUME_CH0() + channel, value_normalized)
    } else if note == 2 {
        dsp_set(ID_GAIN_CH0() + channel, value_normalized)
    }
    // Todo:
    // send_midi(0xB1, 12, dsp_get(ID_DB_METER_CH0()) * 127.0)
}