MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
SpatialTransfer.hpp
Go to the documentation of this file.
1#pragma once
2
4
5namespace MayaFlux::Buffers {
6class NetworkGeometryBuffer;
7class RelaxationGridBuffer;
8}
9
10namespace MayaFlux::Vruta {
11class TaskScheduler;
12}
13
14namespace MayaFlux::IO {
15
16/**
17 * @struct SpatialCaptureSource
18 * @brief One named stream a SpatialCapture (or save_spatial_snapshot) writes
19 * into a SpatialCache each tick.
20 *
21 * capture_frame receives the cache and this source's own stream_name, so one
22 * SpatialCapture can drive several distinct sets into one archive on one
23 * clock: several operators on a network, a network stream alongside a grid
24 * stream, and so on. A caller wanting independent cadences, or several
25 * independent writers landing in the same archive as their own updates
26 * arrive, instead constructs several SpatialCapture instances (or writes
27 * directly through SpatialCache::write()) against the same
28 * shared_ptr<SpatialCache> IOManager::capture_spatial() hands back for a
29 * given filepath; nothing here assumes it is the only writer touching that
30 * cache.
31 */
33 std::string stream_name;
34 std::function<bool(SpatialCache&, const std::string&)> capture_frame;
35};
36
37/**
38 * @brief Build a SpatialCaptureSource that packs a NetworkGeometryBuffer's
39 * driving network through write_network_geometry_buffer_sample()
40 * each tick.
41 * @param stream_name Stream name within the target archive.
42 * @param buffer Source buffer, held for the source's lifetime.
43 */
44[[nodiscard]] MAYAFLUX_API SpatialCaptureSource make_network_geometry_source(
45 std::string stream_name,
46 std::shared_ptr<Buffers::NetworkGeometryBuffer> buffer);
47
48/**
49 * @class SpatialCapture
50 * @brief Records time-sampled spatial data from one or more sources into a
51 * single Alembic archive.
52 *
53 * Unlike VolumeCapture, which numbers a file per frame because no volumetric
54 * format carries time, Alembic holds every sample in the one archive a
55 * SpatialCache keeps open across the whole recording: start() spawns a
56 * GraphicsRoutine that ticks every source in turn and suspends on a
57 * FrameDelay, the same frame-clock-driven, no-polling shape VolumeCapture
58 * uses. stop() cancels the task by name; frames already written stay on
59 * disk.
60 *
61 * A failed source stops the capture, the same as VolumeCapture: a sequence
62 * with a hole in it is worse than a short one.
63 */
64class MAYAFLUX_API SpatialCapture {
65public:
66 /**
67 * @param scheduler Scheduler the capture routine is added to.
68 * @param cache Target cache. Must already be open(); held for the
69 * capture's lifetime, and may be shared with other
70 * SpatialCapture instances writing their own streams
71 * into the same archive.
72 * @param sources Streams ticked together, in order, every frame.
73 */
75 Vruta::TaskScheduler& scheduler,
76 std::shared_ptr<SpatialCache> cache,
77 std::vector<SpatialCaptureSource> sources);
78
80
83
84 /**
85 * @brief Spawn the capture routine, resetting the frame counter.
86 * @param max_frames Stop after this many frames. Zero records until
87 * stop().
88 * @param frame_interval Frames between captures.
89 */
90 void start(uint32_t max_frames = 0, uint64_t frame_interval = 1);
91
92 /**
93 * @brief Request the capture routine stop. Frames already written are
94 * kept.
95 *
96 * Only sets a stop flag and asks the scheduler to drop the task;
97 * returns immediately without waiting for the routine's coroutine to
98 * actually observe it and exit, since that resumption happens on the
99 * graphics thread on its own schedule. This is what makes it safe to
100 * call from any thread, including inside ~SpatialCapture(): the state
101 * the coroutine reads (CaptureState) is reference-counted independently
102 * of this object, so a SpatialCapture destroyed the instant stop()
103 * returns never leaves the still-running coroutine touching freed
104 * memory.
105 */
106 void stop();
107
108 [[nodiscard]] bool is_recording() const;
109 [[nodiscard]] uint32_t frames_written() const;
110
111private:
112 /**
113 * @brief Everything the routine's coroutine touches, held by a
114 * shared_ptr the coroutine copies into its own frame.
115 *
116 * Decouples the running coroutine's lifetime from this SpatialCapture
117 * handle's: the handle can be destroyed (or start() called again) at
118 * any moment without the coroutine, mid-resumption on the graphics
119 * thread, ever dereferencing memory this object owned.
120 */
121 struct CaptureState;
122
123 /**
124 * @brief One tick: write every source's stream.
125 * @return False if any source's capture_frame() call fails, which ends
126 * the routine.
127 *
128 * A private static member, not a free function, because CaptureState is
129 * a private nested type: only SpatialCapture's own members can name it.
130 */
131 static bool run_one_frame(CaptureState& state);
132
134 std::shared_ptr<SpatialCache> m_cache;
135 std::vector<SpatialCaptureSource> m_sources;
136 std::shared_ptr<CaptureState> m_state;
137
138 std::string m_task_name;
139};
140
141/**
142 * @class RelaxationGridCapture
143 * @brief Records time-sampled cell state from a RelaxationGridBuffer into an
144 * Alembic archive.
145 *
146 * RelaxationGridBuffer::snapshot_source() is a single-consumer
147 * BroadcastSource (its own doc: one coroutine per instance, a second
148 * listener silently displaces the first), so this class does not subscribe
149 * to it itself and never will: the caller keeps whatever
150 * Kriya::on_signal(grid->snapshot_source(), ...) listener it already has
151 * and calls write_snapshot() from inside that same callback. This also
152 * keeps RelaxationGridBuffer itself untouched; everything here works from
153 * its existing public accessors.
154 *
155 * Positions and ids are fixed at construction via relaxation_grid_positions()
156 * and reused for every sample, matching that function's own guidance.
157 * Per-cell state format is rule-defined (a float, a uint32 automaton state,
158 * a vec2 reaction-diffusion pair, ...), so the caller supplies the one
159 * genuinely rule-specific piece: how to turn a snapshot's raw bytes into
160 * named attributes.
161 */
162class MAYAFLUX_API RelaxationGridCapture {
163public:
164 using ToAttributes = std::function<std::vector<SpatialAttribute>(std::span<const uint8_t>)>;
165
166 /**
167 * @param grid Source grid. Held for the capture's lifetime.
168 * @param stream_name Stream name within the target archive.
169 * @param extent NDC half-span, forwarded to relaxation_grid_positions().
170 * @param to_attributes Converts one snapshot's raw bytes into named
171 * attributes. Called only from write_snapshot(),
172 * only while capturing.
173 */
175 std::shared_ptr<Buffers::RelaxationGridBuffer> grid,
176 std::string stream_name,
177 float extent,
178 ToAttributes to_attributes);
179
181
186
187 /**
188 * @brief Open the archive. Subsequent write_snapshot() calls append to it.
189 * @return False if SpatialCache::open() fails; not capturing in that case.
190 */
191 bool start(const std::string& filepath);
192
193 /**
194 * @brief Close the archive. No-op if not currently capturing.
195 */
196 void stop();
197
198 [[nodiscard]] bool is_capturing() const { return m_cache != nullptr; }
199
200 /**
201 * @brief Write one snapshot as a sample, if currently capturing.
202 * @param bytes One generation's raw cell state, exactly what the
203 * caller's own snapshot_source() listener received.
204 * @return True if not currently capturing (a no-op still succeeds) or
205 * the sample was written; false if to_attributes() or
206 * SpatialCache::write() fails.
207 */
208 bool write_snapshot(std::span<const uint8_t> bytes);
209
210private:
211 std::shared_ptr<Buffers::RelaxationGridBuffer> m_grid;
212 std::string m_stream_name;
214
215 std::vector<glm::vec3> m_positions;
216 std::vector<uint64_t> m_ids;
217
218 std::shared_ptr<SpatialCache> m_cache;
219};
220
221/**
222 * @brief Open a fresh SpatialCache, write every source once, then close it.
223 *
224 * Splices a millisecond timestamp into @p path_pattern the same way
225 * save_mesh_snapshot does, so repeated calls never collide. A single time
226 * sample rather than a recording: useful for an ad hoc "what does this look
227 * like right now" export.
228 *
229 * @return False if the cache fails to open or any source's capture_frame()
230 * call fails.
231 */
232[[nodiscard]] bool save_spatial_snapshot(
233 const std::string& path_pattern,
234 const std::vector<SpatialCaptureSource>& sources);
235
236} // namespace MayaFlux::IO
std::shared_ptr< NetworkGeometryBuffer > buffer
std::function< std::vector< SpatialAttribute >(std::span< const uint8_t >)> ToAttributes
std::shared_ptr< SpatialCache > m_cache
RelaxationGridCapture(const RelaxationGridCapture &)=delete
RelaxationGridCapture & operator=(const RelaxationGridCapture &)=delete
RelaxationGridCapture(RelaxationGridCapture &&)=delete
RelaxationGridCapture & operator=(RelaxationGridCapture &&)=delete
std::shared_ptr< Buffers::RelaxationGridBuffer > m_grid
Records time-sampled cell state from a RelaxationGridBuffer into an Alembic archive.
Alembic-backed writer for time-sampled spatial entity state: particle systems, point clouds,...
std::shared_ptr< SpatialCache > m_cache
std::vector< SpatialCaptureSource > m_sources
std::shared_ptr< CaptureState > m_state
SpatialCapture(const SpatialCapture &)=delete
Vruta::TaskScheduler & m_scheduler
SpatialCapture & operator=(const SpatialCapture &)=delete
Records time-sampled spatial data from one or more sources into a single Alembic archive.
Token-based multimodal task scheduling system for unified coroutine processing.
Definition Scheduler.hpp:51
bool save_spatial_snapshot(const std::string &path_pattern, const std::vector< SpatialCaptureSource > &sources)
Open a fresh SpatialCache, write every source once, then close it.
SpatialCaptureSource make_network_geometry_source(std::string stream_name, std::shared_ptr< Buffers::NetworkGeometryBuffer > buffer)
Build a SpatialCaptureSource that packs a NetworkGeometryBuffer's driving network through write_netwo...
std::function< bool(SpatialCache &, const std::string &)> capture_frame
One named stream a SpatialCapture (or save_spatial_snapshot) writes into a SpatialCache each tick.