blob: 58a116adb3b04ce79cf10824267057678c7cd4c0 [file]
#ifndef _RIVE_WASM_PROCESSORS_HPP_
#define _RIVE_WASM_PROCESSORS_HPP_
#ifdef WITH_RIVE_SCRIPTING_WASM
#include "rive/async/job_pool.hpp"
#include "rive/async/threading.hpp"
#include "rive/wasm/wasm_lane.hpp"
#include <atomic>
#include <memory>
#include <mutex>
#include <vector>
// A worklet plays processors only from a shared heap, and only tools render
// them offline, so other web builds can only refuse them.
#if !RIVE_ONE_THREAD() || defined(WITH_RIVE_TOOLS)
#define RIVE_PROCESSOR_AUDIO() 1
#else
#define RIVE_PROCESSOR_AUDIO() 0
#endif
namespace rive
{
class AudioEngine;
class AudioRenderer;
class AudioRendererNode;
/// What a processor renders: interleaved frames at a sample rate, in blocks
/// of at most maxFrames.
struct ProcessorFormat
{
uint32_t sampleRate = 48000;
uint32_t channels = 2;
uint32_t maxFrames = 1024;
};
/// A script's AudioProcessor: init runs on a thread of its own, then each
/// block on the audio thread, where overrunning hardBlocks of audio silences
/// only it.
class WasmProcessor : public JobTask, public WasmLane
{
public:
WasmProcessor(
rcp<WasmLaneModule> module,
uint32_t entry,
std::vector<uint8_t> input,
ProcessorFormat format,
uint32_t hardBlocks);
~WasmProcessor() override;
#if RIVE_PROCESSOR_AUDIO()
/// Writes interleaved frames from startFrame on the engine's clock;
/// silence before init has run and after a stop or a failure.
void render(float* out, uint32_t frames, uint64_t startFrame);
#endif
/// Leaves the mix; a render already running finishes its block.
void stop() override;
LaneState state() const override { return m_state.load(); }
const ProcessorFormat& format() const { return m_format; }
// JobTask: instantiates the module and runs init, which nothing joins.
void run() override;
static constexpr uint32_t kEventLog = lane_layout::kProcessorEventLog;
private:
friend class WasmProcessors;
bool startInstance();
#if RIVE_PROCESSOR_AUDIO()
bool process(float* out, uint32_t frames, uint64_t startFrame);
#endif
void endInstance();
/// Moves a starting or running processor to state; a failed or stopped
/// one stays as it is. False when it stayed.
bool settle(LaneState state);
ProcessorFormat m_format;
uint32_t m_hardBlocks;
std::atomic<LaneState> m_state{LaneState::starting};
#ifndef __EMSCRIPTEN__
// A cancel ends an init still running.
void interrupt() override { interruptCall(); }
std::unique_ptr<WasmCallBudget> m_budget;
bool m_warm = false;
void* m_processFn = nullptr;
void* m_collectFn = nullptr;
#elif RIVE_PROCESSOR_AUDIO()
bool startOnPage();
// The page runner's id for this processor's instance.
uint32_t m_webInstance = 0;
public:
/// Hands the processor to the page's audio worklet, which runs it in
/// real time and reports back through event(); false when it refused.
bool startRealtime();
/// The worklet's word on the processor: the state it reached with its
/// text, or kEventLog with a console line.
void event(uint32_t kind, const char* text);
private:
// The worklet's id for it, once handed over.
uint32_t m_audioId = 0;
#endif
#if defined(WITH_RIVE_AUDIO) && !defined(__EMSCRIPTEN__)
// The engine whose mix it joins after init. Attaching and detaching take
// their own lock, since detaching waits for the audio thread.
rcp<AudioEngine> m_engine;
std::unique_ptr<AudioRenderer> m_renderer;
std::mutex m_nodeMutex;
AudioRendererNode* m_node = nullptr;
#endif
};
/// A VM's processors: the module carried in its main module's "rive.audio"
/// section, and every processor the VM started.
class WasmProcessors : public WasmLaneSet<WasmProcessor>
{
public:
using WasmLaneSet::WasmLaneSet;
/// Stops every processor.
~WasmProcessors();
/// Starts a processor that joins the runtime engine's mix after init; null
/// for an entry the module lacks, failed when nothing would play it.
rcp<WasmProcessor> start(
uint32_t entry,
std::vector<uint8_t> input,
uint32_t hardBlocks);
#if RIVE_PROCESSOR_AUDIO()
/// The host pulls blocks through render() itself, in format, so
/// processors run without an audio device or a block budget.
static void setRenderOffline(
bool offline,
const ProcessorFormat& format = ProcessorFormat());
static bool renderOffline();
/// The frames the host has pulled so far in an offline render, which the
/// main lane's audio clock reads. Each render starts at 0.
static void setOfflineFrame(uint64_t frame);
/// The offline render's frame and rate; false unless rendering offline.
static bool offlineClock(uint64_t& frame, uint32_t& sampleRate);
#else
static bool offlineClock(uint64_t&, uint32_t&) { return false; }
#endif
/// Whether a processor's init has yet to run.
bool starting() const;
/// The format a processor started now renders in, all 0 when it would not
/// play.
static ProcessorFormat format();
};
} // namespace rive
#endif
#endif