MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
WalkOperator.hpp
Go to the documentation of this file.
1#pragma once
2
4
6
7/**
8 * @class WalkOperator
9 * @brief Moves a single active slot along the rows of a weight table
10 *
11 * Exactly one slot is meant to be active at a time. advance() reads the row
12 * of the current slot, picks a successor, deactivates the current slot, and
13 * activates the successor, so the slots' leave and enter callbacks fire in
14 * that order. jump() moves directly. Nothing else changes the state.
15 *
16 * WalkOperator can be stepped two ways, and both may be used on the same instance:
17 *
18 * 1. External: call advance() from a node hook, a Vruta or Kriya task, another
19 * operator, or user code. This is sample- or event-accurate, but requires
20 * something outside the network to call it.
21 *
22 * 2. Self-contained: construct with every_n_blocks > 0. process(), which the
23 * network's operator chain already calls once per process_batch() with no
24 * external wiring, then calls advance() itself every N blocks. This is
25 * coarser (block-resolution, not sample-accurate) but requires nothing
26 * outside the network to make the chain progress at all. every_n_blocks is
27 * 0 by default, meaning process() does nothing and only external calls to
28 * advance() move the walk.
29 *
30 * The table is a shared pointer so other operators and writers can read or
31 * edit the same weights, and any cell may be rewritten while the walk runs.
32 * The draw itself uses an owned Kinesis::Stochastic::Stochastic generator
33 * (UNIFORM by default).
34 *
35 * The first call to process(), advance(), or jump() establishes the single-
36 * active-slot state: it activates starting_index (0 unless the constructor
37 * was given a different one) and deactivates every other slot that exists
38 * at that moment, before doing anything else. A slot added by add_slot()
39 * afterward starts at RelationSlot's own default (active) like any other
40 * new slot; WalkOperator does not retroactively enforce its invariant on
41 * it. jump() it explicitly to bring it into the walk.
42 *
43 * Parameters:
44 * - "current": jumps to that slot index when it differs from the current one,
45 * so a mapped Source can steer the walk.
46 *
47 * The table must be square with a dimension equal to the slot count of the
48 * network. advance() and jump() mutate the walk and must not be called
49 * concurrently from more than one thread. Reads are atomic.
50 */
51class MAYAFLUX_API WalkOperator final : public RelationOperator {
52public:
53 /**
54 * @brief Chooses a successor from a row of weights
55 * @param row Outgoing weights of the current slot. Values need not sum to one.
56 * @return Index of the successor. An index outside the row leaves the walk in place.
57 */
58 using Pick = std::function<size_t(std::span<const double> row)>;
59
60 /**
61 * @param table Transition weights, one row per slot
62 * @param pick Selection rule. When empty, Kinesis::Stochastic::Weights::weighted_pick
63 * draws from the row using this operator's own Stochastic generator;
64 * a row that sums to zero leaves the walk in place.
65 * @param every_n_blocks When nonzero, process() calls advance() every this
66 * many process_batch() calls, with no external driver
67 * needed. When zero (the default), process() does
68 * nothing and only explicit advance()/jump() calls,
69 * or a mapped "current" Source, move the walk.
70 * @param starting_index Slot established as active on the first call to
71 * process(), advance(), or jump(). 0 by default.
72 */
73 explicit WalkOperator(std::shared_ptr<Kinesis::Stochastic::Weights> table, Pick pick = {},
74 uint32_t every_n_blocks = 0, size_t starting_index = 0);
75
76 /**
77 * @brief Step once
78 * @return The active slot after the step
79 */
80 size_t advance();
81
82 /**
83 * @brief Make a slot the active one
84 * @param index Slot index. Ignored if out of range for the network's
85 * current slot count or for the table's own row count,
86 * whichever is smaller.
87 */
88 void jump(size_t index);
89
90 [[nodiscard]] size_t current() const noexcept { return m_current.load(std::memory_order_relaxed); }
91 [[nodiscard]] const std::shared_ptr<Kinesis::Stochastic::Weights>& table() const noexcept { return m_table; }
92
93 /**
94 * @brief The generator behind the default weighted draw
95 * @note Not used when a custom Pick was supplied at construction.
96 */
97 [[nodiscard]] Kinesis::Stochastic::Stochastic& generator() noexcept { return m_generator; }
98
99 /**
100 * @brief Self-contained step
101 *
102 * Does nothing when every_n_blocks is 0 (the default). Otherwise counts
103 * blocks and calls advance() every every_n_blocks calls, so the chain
104 * progresses on its own with no code outside the network. Runs regardless
105 * of whether advance() is also being called externally; the two do not
106 * conflict, they simply both move the same walk.
107 */
108 void process(float frames) override;
109
110 /**
111 * @brief Change or disable the self-contained step rate
112 * @param every_n_blocks 0 disables self-stepping; a positive value steps
113 * that often, resetting the internal block counter
114 * @note The block counter itself is not atomic. Calling this concurrently
115 * with process() (i.e. while the owning network is being processed
116 * on another thread, such as the audio thread) is a data race. Call
117 * it from the same thread that drives process_batch(), or before
118 * the network is registered for processing.
119 */
120 void set_every_n_blocks(uint32_t every_n_blocks) noexcept;
121
122 void set_parameter(std::string_view param, double value) override;
123
124 /**
125 * @brief Query walk state
126 * @param query "current" for the active slot index
127 */
128 [[nodiscard]] std::optional<double> query_state(std::string_view query) const override;
129
130 [[nodiscard]] std::string_view get_type_name() const override { return "Walk"; }
131
132private:
133 /**
134 * @brief Activate slot m_current and deactivate every other slot that
135 * currently exists. No-op after the first call, and no-op if no
136 * slots exist yet (tried again on the next call in that case).
137 */
138 void ensure_established();
139
140 std::shared_ptr<Kinesis::Stochastic::Weights> m_table;
142 Kinesis::Stochastic::Stochastic m_generator { Kinesis::Stochastic::Algorithm::UNIFORM };
143 std::atomic<size_t> m_current { 0 };
144 std::atomic<uint32_t> m_every_n_blocks { 0 };
145 uint32_t m_blocks_until { 0 };
146 bool m_established { false };
147};
148
149} // namespace MayaFlux::Nodes::Network
glm::vec3 value
uint32_t index
Definition VKDevice.cpp:122
Unified generative infrastructure for stochastic and procedural algorithms.
Operator that acts on a network's slots over one block at a time.
const std::shared_ptr< Kinesis::Stochastic::Weights > & table() const noexcept
std::function< size_t(std::span< const double > row)> Pick
Chooses a successor from a row of weights.
std::string_view get_type_name() const override
Type name for introspection.
Kinesis::Stochastic::Stochastic & generator() noexcept
The generator behind the default weighted draw.
std::shared_ptr< Kinesis::Stochastic::Weights > m_table
Moves a single active slot along the rows of a weight table.