MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
IOManager.hpp
Go to the documentation of this file.
1#pragma once
2
4
6
9
11
13
16
18
19#include <future>
20
21#include <libavcodec/avcodec.h>
22#include <libavutil/pixfmt.h>
23
24namespace MayaFlux::Core {
25class VKImage;
26struct GlobalStreamInfo;
27}
28
30class MeshNetwork;
31}
32
33namespace MayaFlux::Kakshya {
34class SignalSourceContainer;
35class VideoStreamContainer;
36class VideoFileContainer;
37class SoundFileContainer;
38class CameraContainer;
39class AudioOutputContainer;
40class CompositeContainer;
41}
42
43namespace MayaFlux::Buffers {
44class VideoContainerBuffer;
45class SoundContainerBuffer;
46class TextureBuffer;
47class TextBuffer;
48class MeshBuffer;
49class ComputeMeshBuffer;
50class VolumeGridBuffer;
51class BufferManager;
52}
53
55class IOService;
56class AudioBackendService;
57}
58
59namespace MayaFlux::IO {
60
61using TextureResolver = std::function<std::shared_ptr<Core::VKImage>(const std::string& path)>;
62
63class ImageReader;
64class ModelReader;
65class SoundFileWriter;
66class CameraSource;
67struct CameraConfig;
68class CompositeReader;
69class CompositeWriter;
70class VideoFileWriter;
71class VolumeCapture;
72
80
81/**
82 * @brief Result of load_video(path, config): the video container, then its audio.
83 *
84 * Read it as `auto [video, audio] = load_video(path, config);`. The audio is
85 * nullptr if the file has no audio track or VideoReadOptions::EXTRACT_AUDIO was
86 * not set in the config. Both are nullptr on failure.
87 */
88using VideoLoadResult = std::pair<
89 std::shared_ptr<Kakshya::VideoFileContainer>,
90 std::shared_ptr<Kakshya::SoundFileContainer>>;
91
92/**
93 * @class IOManager
94 * @brief Optional orchestration layer for IO reader lifetime and IOService dispatch.
95 *
96 * MayaFlux IO readers are fully self-sufficient — VideoFileReader, SoundFileReader,
97 * and ImageReader can all be used standalone without this class. IOManager exists
98 * to solve problems that only arise at scale:
99 *
100 * - Globally unique reader_ids across concurrent VideoFileReader instances, so
101 * IOService::request_decode routes to the correct decode thread.
102 * - A single IOService registration backed by a keyed dispatch map, rather than
103 * each reader overwriting the previous registration.
104 * - Shared ownership of VideoFileReader instances whose background decode thread
105 * must outlive the scope in which they were created.
106 * - High-level load/hook functions that collapse the full open→load→processor
107 * setup→buffer wiring sequence into single calls, mirroring what Depot does
108 * for audio and eliminating the boilerplate currently required for video.
109 *
110 * VideoFileReader detects the IOService registration generically via BackendRegistry
111 * and skips its own registration — no IOManager-specific knowledge in the reader.
112 *
113 * Levels of integration (all valid):
114 * 1. VideoFileReader standalone, single stream — self-registers, self-ids.
115 * 2. IOManager constructed directly — multi-stream safe, no Engine required.
116 * 3. Engine constructs IOManager in Init() — fully orchestrated, zero user wiring.
117 */
118class MAYAFLUX_API IOManager {
119public:
120 /**
121 * @brief Construct IOManager and register the IOService into BackendRegistry.
122 *
123 * Must be constructed before any VideoFileReader::load_into_container() call
124 * that should participate in managed dispatch.
125 */
126 IOManager(Core::GlobalStreamInfo& stream_info, uint32_t frame_rate, const std::shared_ptr<Buffers::BufferManager>& buffer_manager, const std::shared_ptr<Vruta::TaskScheduler>& scheduler);
127
128 /**
129 * @brief Calls shutdown() if it has not already run.
130 *
131 * Both stop() and shutdown() are safe to call earlier and explicitly;
132 * the destructor exists only to catch a caller that never did.
133 */
134 ~IOManager();
135
136 IOManager(const IOManager&) = delete;
137 IOManager& operator=(const IOManager&) = delete;
138 IOManager(IOManager&&) = delete;
140
141 /**
142 * @brief Finalizes all in-flight output while consumers are still running.
143 *
144 * Stops every audio, video, volume and spatial capture through its
145 * stop_*_capture() call, closes the sound writers and waits for pending
146 * saves. A capture the caller never stopped is still finalized (an Ogawa
147 * archive or video container left open is not a valid file), and
148 * volume/spatial captures release their scheduler reference here instead
149 * of reaching into an already-destroyed scheduler.
150 *
151 * The IOService stays registered and readers stay alive, so threads that
152 * consume them keep working. Idempotent. Call before the subsystems stop
153 * (see Engine::End()), while backend services and logging sinks are alive.
154 */
155 void stop();
156
157 /**
158 * @brief Unregisters IOService and releases readers, writers, buffers and caches.
159 *
160 * Calls stop() first. Call after the consuming threads have been joined and
161 * before the graphics backend is cleaned up, so objects whose destructors
162 * touch the backend are released while it is still alive. Idempotent.
163 */
164 void shutdown();
165
166 // ─────────────────────────────────────────────────────────────────────────
167 // Video — load
168 // ─────────────────────────────────────────────────────────────────────────
169
170 /**
171 * @brief Load a video file into a VideoFileContainer.
172 *
173 * Performs can_read check, opens the
174 * file with metadata extraction, creates the container, registers the reader
175 * with IOManager (assigning a globally unique reader_id), calls
176 * setup_io_service(), loads into container, and configures the default
177 * FrameAccessProcessor with auto_advance enabled.
178 *
179 * Audio is NOT extracted. Use load_video(path, options) for that.
180 *
181 * @param filepath Path to the video file.
182 * @return Loaded VideoFileContainer, or nullptr on failure.
183 */
184 [[nodiscard]] std::shared_ptr<Kakshya::VideoFileContainer> load_video(const std::string& filepath);
185
186 /**
187 * @brief Load a video file into a VideoFileContainer.
188 *
189 * Performs can_read check, opens with
190 * metadata and region extraction, registers the reader (assigning a globally
191 * unique reader_id), calls setup_io_service(), loads into container, and
192 * configures the default FrameAccessProcessor with auto_advance enabled.
193 *
194 * Passing VideoReadOptions::EXTRACT_AUDIO also extracts the embedded
195 * SoundFileContainer, configures its ContiguousAccessProcessor identically
196 * to load_audio_file() in Depot, and fills the audio half of the result.
197 *
198 * @param filepath Path to the video file.
199 * @param config VideoLoadConfig containing options and target dimensions.
200 * @return The video container and, if requested and present, its audio container.
201 */
202 [[nodiscard]] VideoLoadResult load_video(
203 const std::string& filepath, VideoLoadConfig config);
204
205 // ─────────────────────────────────────────────────────────────────────────
206 // Video — hook
207 // ─────────────────────────────────────────────────────────────────────────
208
209 /**
210 * @brief Wire a video container to the graphics buffer system.
211 *
212 * Creates a VideoContainerBuffer via BufferManager with GRAPHICS_BACKEND token,
213 * stores it keyed by container pointer, and returns it. A VideoFileContainer
214 * or a DynamicVideoStream can be wired. A DynamicVideoStream needs frames or
215 * an enabled ring before it can be hooked, and gets its seek processor.
216 *
217 * @param container Video container to wire.
218 * @return Created VideoContainerBuffer, or nullptr on failure.
219 */
220 [[nodiscard]] std::shared_ptr<Buffers::VideoContainerBuffer>
221 hook_video_container_to_buffer(
222 const std::shared_ptr<Kakshya::VideoStreamContainer>& container);
223
224 // ─────────────────────────────────────────────────────────────────────────
225 // Video — retrieve
226 // ─────────────────────────────────────────────────────────────────────────
227
228 /**
229 * @brief Retrieve the VideoContainerBuffer created for a container.
230 * @param container Container previously passed to hook_video_container_to_buffer().
231 * @return Stored buffer, or nullptr if not found.
232 */
233 [[nodiscard]] std::shared_ptr<Buffers::VideoContainerBuffer>
234 get_video_buffer(
235 const std::shared_ptr<Kakshya::VideoStreamContainer>& container) const;
236
237 // ─────────────────────────────────────────────────────────────────────────
238 // Audio — load
239 // ─────────────────────────────────────────────────────────────────────────
240
241 /**
242 * @brief Load an audio file into a SoundFileContainer.
243 *
244 * Performs can_read check, opens the file, creates and populates the
245 * container, and configures the default ContiguousAccessProcessor
246 * with auto_advance enabled.
247 *
248 * @param filepath Path to the audio file.
249 * @param config LoadConfig struct containing audio read options.
250 * @return Loaded SoundFileContainer, or nullptr on failure.
251 */
252 [[nodiscard]] std::shared_ptr<Kakshya::SoundFileContainer> load_audio(const std::string& filepath, VideoLoadConfig config = {});
253
254 /**
255 * @brief Load an audio file into a fully resident, size-bounded DynamicSoundStream.
256 *
257 * Applies the engine sample rate, ROW_MAJOR layout, and the engine buffer
258 * size as the CursorAccessProcessor block size. The result is ready for use
259 * with StreamSliceProcessor without any further configuration.
260 *
261 * @param filepath Path to the audio file.
262 * @param max_frames Upper bound on frame count. 0 defaults to 5 s at the
263 * engine sample rate inside SoundFileReader::load_bounded.
264 * @param truncate If true, silently truncate files exceeding max_frames.
265 * @return Configured DynamicSoundStream, or nullptr on failure.
266 */
267 [[nodiscard]] std::shared_ptr<Kakshya::DynamicSoundStream> load_audio_bounded(
268 const std::string& filepath,
269 uint64_t max_frames = 0,
270 bool truncate = false);
271
272 // ─────────────────────────────────────────────────────────────────────────
273 // Audio — write
274 // ─────────────────────────────────────────────────────────────────────────
275
276 /**
277 * @brief Construct an open SoundFileWriter the caller drives manually.
278 *
279 * Allocates, opens, and returns a writer ready to accept write() calls.
280 * IOManager does not track this writer; the caller is responsible for
281 * calling close() when done.
282 *
283 * @param filepath Output file path. Extension determines container format.
284 * @param channels Number of channels in all submitted data.
285 * @param sample_rate PCM sample rate in Hz.
286 * @param codec_id Encoder override; AV_CODEC_ID_NONE = container default.
287 * @return Open SoundFileWriter, or nullptr if open() failed.
288 */
289 [[nodiscard]] std::shared_ptr<SoundFileWriter>
290 create_writer(const std::string& filepath,
291 uint32_t channels,
292 uint32_t sample_rate = 48000,
293 AVCodecID codec_id = AV_CODEC_ID_NONE);
294
295 /**
296 * @brief Write a SoundStreamContainer to file in one shot.
297 *
298 * Opens a writer, posts the container's full data, closes, and stores
299 * the encode future in m_save_tasks. Non-blocking; returns immediately.
300 * Container metadata (sample_rate, channels) drives the encoder parameters.
301 *
302 * @param container Source container; any SoundStreamContainer child.
303 * @param filepath Output file path.
304 * @param codec_id Encoder override; AV_CODEC_ID_NONE = container default.
305 */
306 void write(const std::shared_ptr<Kakshya::SoundStreamContainer>& container,
307 const std::string& filepath,
308 AVCodecID codec_id = AV_CODEC_ID_NONE);
309
310 /**
311 * @brief Begin continuous capture of live audio output to a file.
312 *
313 * Registers an observer with AudioBackendService. Each output cycle the
314 * observer drives an AudioOutputContainer and posts its processed_data
315 * to a SoundFileWriter. Returns an opaque capture id for use with
316 * stop_audio_capture().
317 *
318 * Returns 0 and logs an error if AudioBackendService is unavailable.
319 *
320 * @param filepath Output file path.
321 * @param codec_id Encoder override; AV_CODEC_ID_NONE = container default.
322 * @return Capture handle; pass to stop_audio_capture() to finalise.
323 */
324 [[nodiscard]] uint32_t capture_output(const std::string& filepath,
325 AVCodecID codec_id = AV_CODEC_ID_NONE);
326
327 /**
328 * @brief Stop a running audio capture and finalise the file.
329 *
330 * Unregisters the AudioBackendService observer, calls writer->close(),
331 * and stores the encode future in m_save_tasks. Non-blocking.
332 *
333 * @param capture_id Handle returned by capture_output().
334 */
335 void stop_audio_capture(uint32_t capture_id);
336
337 // ─────────────────────────────────────────────────────────────────────────
338 // Audio — hook
339 // ─────────────────────────────────────────────────────────────────────────
340
341 /**
342 * @brief Wire a SoundFileContainer to the audio buffer system.
343 *
344 * Creates per-channel SoundContainerBuffers with AUDIO_BACKEND token,
345 * stores them keyed by container pointer, and returns them.
346 *
347 * @param container Loaded SoundFileContainer to wire.
348 * @return Per-channel SoundContainerBuffers, or empty on failure.
349 */
350 [[nodiscard]] std::vector<std::shared_ptr<Buffers::SoundContainerBuffer>>
351 hook_audio_container_to_buffers(
352 const std::shared_ptr<Kakshya::SoundFileContainer>& container);
353
354 // ─────────────────────────────────────────────────────────────────────────
355 // Audio — retrieve
356 // ─────────────────────────────────────────────────────────────────────────
357
358 /**
359 * @brief Retrieve the SoundContainerBuffers created for a container.
360 * @param container Container previously passed to hook_audio_container_to_buffers().
361 * @return Stored buffer vector, or empty if not found.
362 */
363 [[nodiscard]] std::vector<std::shared_ptr<Buffers::SoundContainerBuffer>>
364 get_audio_buffers(
365 const std::shared_ptr<Kakshya::SoundFileContainer>& container) const;
366
367 /**
368 * @brief Retrieve the SoundFileContainer extracted from a video file.
369 * @param container VideoFileContainer whose audio was extracted during load_video().
370 * @return Extracted SoundFileContainer, or nullptr if not found.
371 */
372 [[nodiscard]] std::shared_ptr<Kakshya::SoundFileContainer>
373 get_extracted_audio(const std::shared_ptr<Kakshya::VideoFileContainer>& container) const;
374
375 // ─────────────────────────────────────────────────────────────────────────
376 // Camera — open
377 // ─────────────────────────────────────────────────────────────────────────
378
379 /**
380 * @brief Open an FFmpeg camera device and create a CameraContainer.
381 *
382 * Convenience wrapper over the backend-agnostic hosting path: constructs
383 * an FFmpegCameraReader, opens the device, creates the container,
384 * configures its FrameAccessProcessor (auto_advance disabled), then
385 * calls register_camera_source() to assign a reader_id and wire the
386 * container's IOService callback via CameraContainer::setup_io().
387 *
388 * A non-FFmpeg backend does not go through this call at all — it opens
389 * itself however its own backend requires, then calls
390 * register_camera_source() directly with the resulting CameraSource.
391 *
392 * After this call, the normal processing pipeline drives frame pulls:
393 * CameraContainer::process_default() → IOService::request_frame(reader_id)
394 * → dispatch_frame_request() → CameraSource::pull_frame_all()
395 * → decode thread → pull_frame() → mutable_frame_ptr() write → READY.
396 *
397 * @param config Device and resolution configuration.
398 * @return Opened CameraContainer, or nullptr on failure.
399 */
400 [[nodiscard]] std::shared_ptr<Kakshya::CameraContainer>
401 open_camera(const CameraConfig& config);
402
403 // ─────────────────────────────────────────────────────────────────────────
404 // Camera — hook
405 // ─────────────────────────────────────────────────────────────────────────
406
407 /**
408 * @brief Wire a CameraContainer to the graphics buffer system.
409 *
410 * Creates a VideoContainerBuffer via BufferManager with GRAPHICS_BACKEND
411 * token, stores it keyed by container pointer, and returns it. Identical
412 * to hook_video_container_to_buffer() but accepts CameraContainer.
413 *
414 * @param container Opened CameraContainer from open_camera().
415 * @return Created VideoContainerBuffer, or nullptr on failure.
416 */
417 [[nodiscard]] std::shared_ptr<Buffers::VideoContainerBuffer>
418 hook_camera_to_buffer(const std::shared_ptr<Kakshya::CameraContainer>& container);
419
420 // ─────────────────────────────────────────────────────────────────────────
421 // Camera — retrieve
422 // ─────────────────────────────────────────────────────────────────────────
423
424 /**
425 * @brief Retrieve the VideoContainerBuffer created for a camera container.
426 */
427 [[nodiscard]] std::shared_ptr<Buffers::VideoContainerBuffer>
428 get_camera_buffer(const std::shared_ptr<Kakshya::CameraContainer>& container) const;
429
430 // ─────────────────────────────────────────────────────────────────────────
431 // Camera source management
432 // ─────────────────────────────────────────────────────────────────────────
433
434 /**
435 * @brief Assign a globally unique reader_id and take ownership of a
436 * CameraSource that is already open.
437 *
438 * The backend-agnostic entry point for camera hosting: any CameraSource
439 * implementation, from any backend, is registered identically. open()
440 * and configuration are entirely the caller's concern beforehand — this
441 * call never touches either.
442 *
443 * @param source Fully-opened CameraSource.
444 * @return Globally unique reader_id assigned to this source.
445 */
446 [[nodiscard]] uint64_t register_camera_source(std::shared_ptr<CameraSource> source);
447
448 /**
449 * @brief Release ownership of the camera source identified by reader_id.
450 *
451 * No-op with a warning if reader_id is unknown.
452 *
453 * @param reader_id Id returned by register_camera_source().
454 */
455 void release_camera_source(uint64_t reader_id);
456
457 // ─────────────────────────────────────────────────────────────────────────
458 // Video reader management
459 // ─────────────────────────────────────────────────────────────────────────
460
461 /**
462 * @brief Assign a globally unique reader_id and take ownership of a reader.
463 *
464 * Called internally by load_video(). Advanced users who open a
465 * VideoFileReader manually can call this before load_into_container() to
466 * participate in managed dispatch and lifetime tracking.
467 *
468 * Calls reader->set_reader_id(id) before returning.
469 *
470 * @param reader Fully-opened VideoFileReader.
471 * @return Globally unique reader_id assigned to this reader.
472 */
473 [[nodiscard]] uint64_t register_video_reader(std::shared_ptr<VideoFileReader> reader);
474
475 /**
476 * @brief Release ownership of the reader identified by reader_id.
477 *
478 * When the last shared_ptr to the reader is released its decode thread
479 * is joined. Safe to call from a container state-change callback.
480 * No-op with a warning if reader_id is unknown.
481 *
482 * @param reader_id Id returned by register_video_reader().
483 */
484 void release_video_reader(uint64_t reader_id);
485
486 // ─────────────────────────────────────────────────────────────────────────
487 // Video — write
488 // ─────────────────────────────────────────────────────────────────────────
489
490 /**
491 * @brief Construct an open VideoFileWriter the caller drives manually.
492 *
493 * Allocates and opens the writer. IOManager does not track this writer;
494 * the caller is responsible for calling close() or stop_recording() when
495 * done.
496 *
497 * @param filepath Output file path. Extension determines container.
498 * @param width Frame width in pixels.
499 * @param height Frame height in pixels.
500 * @param frame_rate Average frame rate in frames per second.
501 * @param src_pixel_format AVPixelFormat of submitted pixel data.
502 * @param codec_id Encoder override; AV_CODEC_ID_NONE = container default.
503 * @return Open VideoFileWriter, or nullptr if open() failed.
504 */
505 [[nodiscard]] std::shared_ptr<VideoFileWriter>
506 create_writer(const std::string& filepath,
507 uint32_t width,
508 uint32_t height,
509 double frame_rate,
510 AVPixelFormat src_pixel_format,
511 AVCodecID codec_id = AV_CODEC_ID_NONE);
512
513 /**
514 * @brief Begin continuous capture of a window's rendered frames to a file.
515 *
516 * Delegates to VideoFileWriter::record(). The encoder is opened lazily on
517 * the first delivered frame so pixel format and dimensions come from the
518 * live swapchain. Returns an opaque capture id for use with
519 * stop_video_capture().
520 *
521 * Returns 0 if the window is null or record() fails.
522 *
523 * @param window Window whose swapchain frames will be captured.
524 * @param filepath Output file path. Extension determines container format.
525 * @param frame_rate Nominal frame rate written into the container header.
526 * @param codec_id Encoder override; AV_CODEC_ID_NONE = container default.
527 * @return Capture handle; pass to stop_video_capture() to finalise.
528 */
529 [[nodiscard]] uint32_t capture_window(
530 const std::shared_ptr<Core::Window>& window,
531 const std::string& filepath,
532 double frame_rate,
533 AVCodecID codec_id = AV_CODEC_ID_NONE);
534
535 /**
536 * @brief Stop a running video capture and finalise the file.
537 *
538 * Calls writer->stop_recording(), the counterpart to the record() call
539 * capture_window() made, and stores the encode future in m_save_tasks.
540 * Non-blocking.
541 *
542 * @param capture_id Handle returned by capture_window().
543 */
544 void stop_video_capture(uint32_t capture_id);
545
546 /**
547 * @brief Stop the active window capture and finalise the file.
548 *
549 * Convenience overload that resolves the capture id by window pointer,
550 * then calls stop_video_capture(). No-op with a warning if no capture
551 * is active for this window.
552 *
553 * @param window Window previously passed to capture_window().
554 */
555 void stop_capture(const std::shared_ptr<Core::Window>& window);
556
557 /**
558 * @brief Returns all active video capture IDs (capture_window()).
559 */
560 [[nodiscard]] std::vector<uint32_t> get_video_capture_ids() const;
561
562 /**
563 * @brief Returns all VideoFileWriters created via create_writer().
564 */
565 [[nodiscard]] std::vector<std::shared_ptr<VideoFileWriter>> get_video_writers() const;
566
567 // ─────────────────────────────────────────────────────────────────────────
568 // Image — load
569 // ─────────────────────────────────────────────────────────────────────────
570
571 /**
572 * @brief Load an image file into a TextureBuffer.
573 *
574 * Opens the file via ImageReader, decodes to the requested channel layout,
575 * and creates a TextureBuffer containing the pixel data. The reader is retained
576 * for the lifetime of IOManager.
577 *
578 * @param filepath Path to the image file (PNG, JPG, BMP, TGA, etc.).
579 * @param desired_channels Channel count passed to ImageReader (0 keeps the source layout).
580 * @param region Optional source-pixel rectangle with inclusive end coordinates.
581 * @return TextureBuffer with pixel data, or nullptr on failure.
582 */
583 [[nodiscard]] std::shared_ptr<Buffers::TextureBuffer>
584 load_image(const std::string& filepath, int desired_channels = 4,
585 std::optional<Kakshya::Region> region = std::nullopt);
586
587 /**
588 * @brief Open a schema-bearing reader selected by file format.
589 *
590 * CSV and TSV currently use DelimitedTextReader. The returned reader is
591 * owned by the caller and can inspect its layout or read bounded batches.
592 * IOManager does not retain it or register it with IOService.
593 *
594 * @param filepath Path to a supported Composite file.
595 * @param layout Optional exact field layout. Without one, CSV/TSV fields
596 * are preserved as UTF-8 text.
597 * @return Open reader, or nullptr for an unsupported or invalid file.
598 */
599 [[nodiscard]] std::shared_ptr<CompositeReader>
600 open_composite_reader(
601 const std::string& filepath,
602 std::optional<Kakshya::CompositeLayout> layout = std::nullopt);
603
604 /**
605 * @brief Load a complete Composite file into an owning container.
606 *
607 * Dispatches by supported file format. The reader is local to this call;
608 * use open_composite_reader() for bounded reading of large files.
609 *
610 * @param filepath Path to a supported Composite file.
611 * @param layout Optional exact field layout for typed CSV/TSV values.
612 * @param batch_size Default container materialization batch size.
613 * @return Owning CompositeContainer, or nullptr on failure.
614 */
615 [[nodiscard]] std::shared_ptr<Kakshya::CompositeContainer>
616 load_composite(
617 const std::string& filepath,
618 std::optional<Kakshya::CompositeLayout> layout = std::nullopt,
619 size_t batch_size = 1);
620
621 /**
622 * @brief Open a Composite writer selected by the output file format.
623 *
624 * CSV and TSV currently use DelimitedTextWriter. The caller owns the
625 * returned writer and must call write_rows() and close(). IOManager
626 * does not retain it or enqueue its writes.
627 *
628 * @param filepath Destination path with a supported extension.
629 * @param layout Field layout for every slice written to the file.
630 * @return Open writer, or nullptr if the format or layout is unsupported
631 * or the file could not be opened.
632 */
633 [[nodiscard]] std::shared_ptr<CompositeWriter>
634 open_composite_writer(
635 const std::string& filepath,
636 const Kakshya::CompositeLayout& layout);
637
638 /**
639 * @brief Save a complete CompositeContainer in a supported format.
640 *
641 * The write is synchronous, unlike the queued save_image, save_mesh, and
642 * save_volume. For bounded writes of large data, use
643 * open_composite_writer() and supply slices directly. A partial file
644 * may remain on failure.
645 *
646 * @param container Source container.
647 * @param filepath Destination CSV or TSV path.
648 * @return True only if every element was written and the file closed.
649 */
650 bool save_composite(
651 const std::shared_ptr<Kakshya::CompositeContainer>& container,
652 const std::string& filepath);
653
654 // ─────────────────────────────────────────────────────────────────────────
655 // Mesh — load
656 // ─────────────────────────────────────────────────────────────────────────
657
658 /**
659 * @brief Load all meshes from a 3D model file into MeshBuffer instances.
660 *
661 * Opens the file via ModelReader, extracts all aiMesh entries as MeshData,
662 * constructs one MeshBuffer per mesh, calls setup_processors() on each,
663 * and returns them. setup_rendering() is left to the caller.
664 *
665 * If resolver is null, the default resolver is used: paths resolved
666 * relative to the model file's directory via ImageReader::load_texture.
667 * Unresolvable textures are logged and skipped.
668 *
669 * @param filepath Path to the model file (glTF, FBX, OBJ, PLY, etc.).
670 * @param resolver Optional texture resolver.
671 * @return One MeshBuffer per mesh in scene order, or empty on failure.
672 */
673 [[nodiscard]] std::vector<std::shared_ptr<Buffers::MeshBuffer>>
674 load_mesh(
675 const std::string& filepath,
676 TextureResolver resolver = nullptr);
677
678 /**
679 * @brief Load a 3D model file as a MeshNetwork.
680 *
681 * One MeshSlot per aiMesh. Per-slot diffuse textures resolved via resolver.
682 * Null resolver uses the default: ImageReader::load_texture relative to the
683 * model file's directory.
684 *
685 * @param filepath Path to the model file.
686 * @param resolver Optional texture resolver.
687 * @return Populated MeshNetwork, or nullptr on failure.
688 */
689 [[nodiscard]] std::shared_ptr<Nodes::Network::MeshNetwork>
690 load_mesh_network(
691 const std::string& filepath,
692 TextureResolver resolver = nullptr);
693
694 // ─────────────────────────────────────────────────────────────────────────
695 // Mesh - save
696 // ─────────────────────────────────────────────────────────────────────────
697
698 /**
699 * @brief Save a MeshBuffer's mesh data to disk asynchronously.
700 *
701 * MeshData is always CPU-authoritative for MeshBuffer, so there is no
702 * download to wait on, but the write itself (AssimpModelWriter ->
703 * Assimp::Exporter -> disk) is still blocking I/O; queuing it here keeps
704 * that off the caller's thread, the same reason save_image/save_volume
705 * do. Returns once the task is queued; write failure is logged from the
706 * task and does not surface here.
707 *
708 * @param mesh_buffer Source mesh. Must have valid MeshData.
709 * @param filepath Destination path with extension.
710 * @param options Format-specific writer options.
711 * @return True once the task is queued.
712 */
713 bool save_mesh(
714 const std::shared_ptr<Buffers::MeshBuffer>& mesh_buffer,
715 const std::string& filepath,
716 const IO::ModelWriteOptions& options = {});
717
718 /**
719 * @brief Save one or more meshes to disk asynchronously.
720 *
721 * For callers that already have MeshData in hand (e.g. from
722 * IO::download_compute_mesh) rather than a MeshBuffer. Every entry must
723 * satisfy Kakshya::MeshData::is_valid(). Returns once the task is queued.
724 */
725 bool save_mesh(
726 const std::vector<Kakshya::MeshData>& meshes,
727 const std::string& filepath,
728 const IO::ModelWriteOptions& options = {});
729
730 /**
731 * @brief Save a MeshNetwork's current slots to disk asynchronously.
732 *
733 * Slot data lives on MeshWriterNode, CPU-resident the same way
734 * MeshBuffer's is, whether or not the network is wrapped in a
735 * MeshNetworkBuffer or has ever been rendered. Each slot's vertices are
736 * baked into world space from its current world_transform. Returns
737 * once the task is queued.
738 *
739 * @param network Source network.
740 * @param filepath Destination path with extension.
741 * @param options Format-specific writer options.
742 * @return True once the task is queued.
743 */
744 bool save_mesh(
745 const std::shared_ptr<Nodes::Network::MeshNetwork>& network,
746 const std::string& filepath,
747 const IO::ModelWriteOptions& options = {});
748
749 /**
750 * @brief Save a ComputeMeshBuffer's current live geometry to disk asynchronously.
751 *
752 * ComputeMeshBuffer's geometry lives GPU-only, so this queues a real
753 * device-to-host transfer (IO::download_compute_mesh) on top of the
754 * disk write every overload above also queues. Returns once the task
755 * is queued; readback or write failure is logged from the task and
756 * does not surface here.
757 *
758 * @param buffer Source buffer. setup_processors() must have run.
759 * @param filepath Destination path with extension.
760 * @param options Format-specific writer options.
761 * @return True once the task is queued.
762 */
763 bool save_mesh(
764 const std::shared_ptr<Buffers::ComputeMeshBuffer>& buffer,
765 const std::string& filepath,
766 const IO::ModelWriteOptions& options = {});
767
768 /**
769 * @brief Save a ComputeMeshBuffer with a millisecond epoch timestamp
770 * spliced into the path, queued asynchronously.
771 *
772 * The timestamp is computed on the worker thread at the moment the
773 * readback actually runs, not at the moment this call returns, so
774 * repeated presses in quick succession still each land in their own
775 * file rather than racing on a single path computed up front.
776 */
778 const std::shared_ptr<Buffers::ComputeMeshBuffer>& buffer,
779 const std::string& path_pattern,
780 const IO::ModelWriteOptions& options = {});
781
782 // ─────────────────────────────────────────────────────────────────────────
783 // Image — save
784 // ─────────────────────────────────────────────────────────────────────────
785
786 /**
787 * @brief Save image data to disk asynchronously.
788 *
789 * The source is downloaded from the GPU on the calling thread (must have
790 * command queue access) and the encode + disk flush is dispatched to a
791 * worker. Returns once the download completes and the encode task is
792 * queued; encode/flush failures are logged but do not block the caller.
793 *
794 * For synchronous semantics (e.g. shutdown flush, tests) use the
795 * IO::save_image / IO::save_texture_buffer free functions in
796 * ImageExport.hpp.
797 *
798 * The extension of @p filepath selects the writer via
799 * ImageWriterRegistry. Format-variant compatibility is the writer's
800 * responsibility (PNG wants uint8, EXR wants float, etc.).
801 *
802 * @return True if the download succeeded and the encode task was queued.
803 */
804 bool save_image(
805 const std::shared_ptr<Core::VKImage>& image,
806 const std::string& filepath,
807 const IO::ImageWriteOptions& options = {});
808
809 bool save_image(
810 const std::shared_ptr<Buffers::TextureBuffer>& buffer,
811 const std::string& filepath,
812 const IO::ImageWriteOptions& options = {});
813
814 bool save_image(
815 const std::shared_ptr<Buffers::TextBuffer>& buffer,
816 const std::string& filepath,
817 const IO::ImageWriteOptions& options = {});
818
819 /**
820 * @brief Save an already-downloaded ImageData to disk asynchronously.
821 *
822 * For callers that have an ImageData in hand (e.g. from
823 * IO::download_image) and want to avoid the GPU download step. Purely
824 * CPU-bound from here, so no thread restrictions on the caller.
825 */
826 bool save_image(
827 IO::ImageData data,
828 const std::string& filepath,
829 const IO::ImageWriteOptions& options = {});
830
831 /**
832 * @brief Wait for all in-flight save operations to complete.
833 *
834 * Called automatically by the destructor. Invoke explicitly to flush
835 * pending saves before a checkpoint.
836 */
837 void wait_for_pending_saves();
838
839 // ─────────────────────────────────────────────────────────────────────────
840 // Volume — load
841 // ─────────────────────────────────────────────────────────────────────────
842
843 /**
844 * @brief Load a volume file into a newly constructed VolumeGridBuffer.
845 *
846 * Opens the file via IO::VolumeReader, reads every selected grid into
847 * VolumeData, constructs a VolumeGridBuffer with one FieldDecl per
848 * field — name and semantics from the file, stride sizeof(float) for a
849 * scalar field or sizeof(glm::vec4) for a vector one, the GPU layout
850 * every vector field uses regardless of what VolumeData stores — over
851 * the file's own lattice, and seeds it via IO::upload_volume. Mirrors
852 * load_mesh: construction and GPU registration only, nothing about
853 * processing chain or rendering.
854 *
855 * setup_processors() and any simulation wiring via setup_flow() are
856 * left to the caller, as VolumeGridBuffer's own usage example assumes.
857 * Nothing about an imported file says whether the caller wants to keep
858 * simulating what it loaded, run it once as a mask, or read it once and
859 * discard the buffer — so nothing here decides that for them.
860 *
861 * The returned buffer is retained for the lifetime of IOManager; see
862 * get_loaded_volumes().
863 *
864 * @param filepath Path to the volume file (.vdb).
865 * @param options Grid selection, forwarded to VolumeReader::load().
866 * @return Constructed and seeded VolumeGridBuffer, or nullptr on failure.
867 */
868 [[nodiscard]] std::shared_ptr<Buffers::VolumeGridBuffer>
869 load_volume(
870 const std::string& filepath,
871 const IO::VolumeReadOptions& options = {});
872
873 /**
874 * @brief Returns all VolumeGridBuffers created via load_volume().
875 */
876 [[nodiscard]] std::vector<std::shared_ptr<Buffers::VolumeGridBuffer>>
877 get_loaded_volumes() const { return m_loaded_volumes; }
878
879 // ─────────────────────────────────────────────────────────────────────────
880 // Volume — save
881 // ─────────────────────────────────────────────────────────────────────────
882
883 /**
884 * @brief Save an already-downloaded VolumeData to disk asynchronously.
885 *
886 * Purely CPU-bound from here, so no thread restrictions on the caller.
887 * Encode and disk flush are dispatched to a worker; failures are logged
888 * but do not block.
889 *
890 * The extension of @p filepath selects the writer via
891 * VolumeWriterRegistry. For synchronous semantics use the
892 * IO::save_volume free function in VolumeTransfer.hpp, and for a numbered
893 * frame sequence use IO::VolumeCapture.
894 *
895 * @return True if the encode task was queued.
896 */
897 bool save_volume(
899 const std::string& filepath,
900 const IO::VolumeWriteOptions& options = {});
901
902 /**
903 * @brief Download and save a GPU-resident volume to disk asynchronously.
904 *
905 * Mirrors save_image(VKImage): download, writer lookup and write all
906 * happen inside the dispatched task, so this returns as soon as the
907 * task is queued rather than blocking the caller. The future is tracked
908 * in the same pool as every other save task; wait_for_pending_saves()
909 * waits on it like any other.
910 *
911 * @param volume Volume to read from.
912 * @param filepath Destination path; extension selects the writer.
913 * @param options Writer options.
914 * @param field_names Fields to save. Empty means every declared field.
915 * @return True once the task is queued. Download or write failure is
916 * logged from the task and does not surface here.
917 */
918 bool save_volume(
919 const std::shared_ptr<Buffers::VolumeGridBuffer>& volume,
920 const std::string& filepath,
921 const IO::VolumeWriteOptions& options = {},
922 const std::vector<std::string>& field_names = {});
923
924 /**
925 * @brief Begin recording a numbered .vdb sequence from a volume.
926 *
927 * Constructs a VolumeCapture on the engine scheduler and retains it.
928 * The capture spawns a GraphicsRoutine that reads one frame and
929 * suspends on a FrameDelay, so it advances on the frame clock and
930 * resumes on the thread that has command queue access. Nothing polls
931 * it and nothing drives it.
932 *
933 * The readback stays on the graphics thread because it must. The
934 * encode is dispatched to the save task pool, so the per-frame cost on
935 * the graphics thread is the transfer alone.
936 *
937 * For a capture the caller owns and drives itself, construct
938 * IO::VolumeCapture directly.
939 *
940 * @param volume Volume to record.
941 * @param path_pattern Destination with one index field, such as
942 * "smoke.{:04}.vdb". Zero padding matters.
943 * @param field_names Fields to record. Empty means all.
944 * @param options Writer options, applied to every frame.
945 * @param max_frames Stop after this many frames. Zero is unbounded.
946 * @param frame_interval Frames between captures.
947 * @return Capture handle for stop_volume_capture, or 0 on failure.
948 */
949 [[nodiscard]] uint32_t capture_volume(
950 const std::shared_ptr<Buffers::VolumeGridBuffer>& volume,
951 const std::string& path_pattern,
952 const std::vector<std::string>& field_names = {},
953 const IO::VolumeWriteOptions& options = {},
954 uint32_t max_frames = 0,
955 uint64_t frame_interval = 1);
956
957 /**
958 * @brief Stop a running volume capture and release it.
959 *
960 * Frames already queued for encode still complete. No-op with a
961 * warning if the id is unknown.
962 */
963 void stop_volume_capture(uint32_t capture_id);
964
965 /**
966 * @brief Returns all active volume capture IDs.
967 */
968 [[nodiscard]] std::vector<uint32_t> get_volume_capture_ids() const;
969
970 // ─────────────────────────────────────────────────────────────────────────
971 // Spatial — capture
972 // ─────────────────────────────────────────────────────────────────────────
973
974 /**
975 * @brief Begin (or join) recording time-sampled streams into an Alembic
976 * archive.
977 *
978 * Resolves @p filepath to a shared SpatialCache: the first call for a
979 * given path opens it, and a later call passing the same path reuses the
980 * cache already open, so several independent captures can each record
981 * their own streams into one archive. Unlike capture_volume, nothing is
982 * numbered per frame: Alembic carries time natively, so every sample of
983 * every stream lands in the one file across the whole recording.
984 *
985 * Spawns a SpatialCapture on the engine scheduler and retains it, the
986 * same GraphicsRoutine/FrameDelay shape capture_volume uses. For a
987 * capture the caller owns and drives itself, construct IO::SpatialCapture
988 * directly against a SpatialCache of its own.
989 *
990 * @param filepath Destination .abc path.
991 * @param sources Streams to tick together every frame. Build one
992 * with IO::make_network_geometry_source() or a
993 * hand-written IO::SpatialCaptureSource.
994 * @param max_frames Stop after this many frames. Zero is unbounded.
995 * @param frame_interval Frames between captures.
996 * @return Capture handle for stop_spatial_capture, or 0 on failure
997 * (no sources, no scheduler, or the archive failed to open).
998 */
999 [[nodiscard]] uint32_t capture_spatial(
1000 const std::string& filepath,
1001 std::vector<IO::SpatialCaptureSource> sources,
1002 uint32_t max_frames = 0,
1003 uint64_t frame_interval = 1);
1004
1005 /**
1006 * @brief Stop a running spatial capture and release it.
1007 *
1008 * Frames already written are kept. The underlying archive stays open and
1009 * registered under its filepath as long as any other capture still
1010 * shares it. No-op with a warning if the id is unknown.
1011 */
1012 void stop_spatial_capture(uint32_t capture_id);
1013
1014 /**
1015 * @brief Returns all active spatial capture IDs.
1016 */
1017 [[nodiscard]] std::vector<uint32_t> get_spatial_capture_ids() const;
1018
1019 /**
1020 * @brief Save every source once, as a single time sample, with a
1021 * millisecond epoch timestamp spliced into the path, queued
1022 * asynchronously.
1023 *
1024 * The timestamp is computed on the worker thread at the moment the
1025 * write actually runs, mirroring save_mesh_snapshot, so repeated calls
1026 * in quick succession still each land in their own file.
1027 *
1028 * @return True once the task is queued; open or write failure is
1029 * logged from the task and does not surface here.
1030 */
1032 std::vector<IO::SpatialCaptureSource> sources,
1033 const std::string& path_pattern);
1034
1035 /**
1036 * @brief Returns all active video reader IDs.
1037 */
1038 [[nodiscard]] std::vector<uint64_t> get_video_reader_ids() const;
1039
1040 /**
1041 * @brief Returns the video reader assigned to the given ID, or nullptr.
1042 */
1043 [[nodiscard]] std::shared_ptr<VideoFileReader> get_video_reader(uint64_t id) const;
1044
1045 /**
1046 * @brief Returns all active camera reader IDs.
1047 */
1048 [[nodiscard]] std::vector<uint64_t> get_camera_reader_ids() const;
1049
1050 /**
1051 * @brief Returns the camera source assigned to the given ID, or nullptr.
1052 */
1053 [[nodiscard]] std::shared_ptr<CameraSource> get_camera_reader(uint64_t id) const;
1054
1055 /**
1056 * @brief Returns all retained audio readers.
1057 */
1058 [[nodiscard]] std::vector<std::shared_ptr<SoundFileReader>> get_audio_readers() const { return m_audio_readers; };
1059
1060 /**
1061 * @brief Returns all retained image readers.
1062 */
1063 [[nodiscard]] std::vector<std::shared_ptr<ImageReader>> get_image_readers() const { return m_image_readers; };
1064
1065 /**
1066 * @brief Returns all retained model readers.
1067 */
1068 [[nodiscard]] std::vector<std::shared_ptr<ModelReader>> get_model_readers() const { return m_model_readers; };
1069
1070 /**
1071 * @brief Returns all active audio capture IDs.
1072 */
1073 [[nodiscard]] std::vector<uint32_t> get_audio_capture_ids() const;
1074
1075 /**
1076 * @brief Returns all registered SoundFileWriters created via create_writer() or write().
1077 */
1078 [[nodiscard]] std::vector<std::shared_ptr<SoundFileWriter>> get_sound_writers() const { return m_writers; };
1079
1080private:
1083
1084 std::atomic<bool> m_stop_done { false };
1085 std::atomic<bool> m_shutdown_done { false };
1086
1087 /**
1088 * @brief IOService::request_decode target — shared-lock lookup + signal_decode().
1089 * Non-blocking. Safe from any thread.
1090 */
1091 void dispatch_decode_request(uint64_t reader_id);
1092
1093 /**
1094 * @brief IOService::request_frame target — shared-lock lookup + pull_frame_all().
1095 * Non-blocking. Safe from any thread.
1096 */
1097 void dispatch_frame_request(uint64_t reader_id);
1098
1099 /**
1100 * @brief Retain a save task in m_save_tasks and opportunistically reclaim
1101 * finished ones.
1102 *
1103 * Lock-free: delegates to Parallel::AsyncGroup, which never blocks one
1104 * caller on another's future. Failed saves are logged through the
1105 * group's exception handler set up in the constructor, which reuses
1106 * Journal::error_rethrow for the catch(const std::exception&) vs
1107 * catch(...) message dispatch and then swallows the rethrow, since
1108 * AsyncGroup's contract is to report a task's exception, not propagate
1109 * it into whichever thread happens to call submit()/drain() next.
1110 * Nothing about a task's own work (already running on its own thread
1111 * via std::async before this is called) is affected by tracking it here.
1112 */
1113 void track_save_task(std::future<bool> fut);
1114
1115 void configure_frame_processor(
1116 const std::shared_ptr<Kakshya::VideoFileContainer>& container) const;
1117
1118 void configure_audio_processor(
1119 const std::shared_ptr<Kakshya::SoundFileContainer>& container) const;
1120
1121 // ── Audio capture ──────────────────────────────────────────────────────
1122
1124 std::shared_ptr<Kakshya::AudioOutputContainer> container;
1125 std::shared_ptr<SoundFileWriter> writer;
1126 uint32_t observer_id {};
1127 };
1128
1129 mutable std::mutex m_audio_captures_mutex;
1130 std::unordered_map<uint32_t, AudioCaptureState> m_audio_captures;
1131
1132 // ── Audio capture ──────────────────────────────────────────────────────
1133
1135 std::shared_ptr<VideoFileWriter> writer;
1136 uint32_t capture_id {};
1137 };
1138
1139 mutable std::mutex m_video_captures_mutex;
1140 std::unordered_map<uint32_t, VideoCaptureState> m_video_captures;
1141 std::atomic<uint32_t> m_next_video_capture_id { 1 };
1142
1143 std::vector<std::shared_ptr<VideoFileWriter>> m_video_writers;
1144
1145 // ── Volume ──────────────────────────────────────────────────────
1146
1147 std::atomic<uint32_t> m_next_volume_capture_id { 1 };
1148 mutable std::mutex m_volume_captures_mutex;
1149 std::unordered_map<uint32_t, std::unique_ptr<IO::VolumeCapture>> m_volume_captures;
1150 std::vector<std::shared_ptr<Buffers::VolumeGridBuffer>> m_loaded_volumes;
1151
1152 // ── Spatial ──────────────────────────────────────────────────────
1153
1155 std::unique_ptr<IO::SpatialCapture> capture;
1156 std::string filepath; ///< Keys m_spatial_cache_refcounts.
1157 };
1158
1159 std::atomic<uint32_t> m_next_spatial_capture_id { 1 };
1160 mutable std::mutex m_spatial_captures_mutex;
1161 std::unordered_map<uint32_t, SpatialCaptureEntry> m_spatial_captures;
1162 std::unordered_map<std::string, std::shared_ptr<IO::SpatialCache>> m_spatial_caches;
1163 std::unordered_map<std::string, int> m_spatial_cache_refcounts; ///< Closes+erases a cache at zero.
1164
1165 // ── readers ──────────────────────────────────────────────────────
1166
1167 std::atomic<uint64_t> m_next_reader_id { 1 };
1168 mutable std::shared_mutex m_readers_mutex;
1169 mutable std::shared_mutex m_camera_mutex;
1170 std::unordered_map<uint64_t, std::shared_ptr<VideoFileReader>> m_video_readers;
1171 std::unordered_map<uint64_t, std::shared_ptr<CameraSource>> m_camera_readers;
1172
1173 std::vector<std::shared_ptr<SoundFileReader>> m_audio_readers;
1174
1175 std::vector<std::shared_ptr<ImageReader>> m_image_readers;
1176
1177 std::vector<std::shared_ptr<ModelReader>> m_model_readers;
1178
1180 std::vector<std::shared_ptr<SoundFileWriter>> m_writers;
1181
1182 // ── Stored buffers ─────────────────────────────────────────────────────
1183
1184 mutable std::shared_mutex m_buffers_mutex;
1186
1187 std::unordered_map<
1188 std::shared_ptr<Kakshya::VideoStreamContainer>,
1189 std::shared_ptr<Buffers::VideoContainerBuffer>>
1191
1192 std::unordered_map<
1193 std::shared_ptr<Kakshya::CameraContainer>,
1194 std::shared_ptr<Buffers::VideoContainerBuffer>>
1196
1197 std::unordered_map<
1198 std::shared_ptr<Kakshya::VideoFileContainer>,
1199 std::shared_ptr<Kakshya::SoundFileContainer>>
1201
1202 std::unordered_map<
1203 std::shared_ptr<Kakshya::SoundFileContainer>,
1204 std::vector<std::shared_ptr<Buffers::SoundContainerBuffer>>>
1206
1207 std::shared_ptr<Buffers::BufferManager> m_buffer_manager;
1208
1209 std::shared_ptr<Vruta::TaskScheduler> m_scheduler;
1210
1211 // ── IOService ──────────────────────────────────────────────────────────
1212
1213 std::shared_ptr<Registry::Service::IOService> m_io_service;
1214 Registry::Service::AudioBackendService* m_audio_backend_service {};
1215};
1216
1217} // namespace MayaFlux::IO
Core::GlobalNetworkConfig network
Definition Config.cpp:43
IO::ImageData image
Definition Decoder.cpp:47
std::shared_ptr< NetworkGeometryBuffer > buffer
std::weak_ptr< Core::Window > window
Definition Picker.cpp:23
uint32_t channels
uint32_t width
uint32_t height
std::unordered_map< std::shared_ptr< Kakshya::VideoFileContainer >, std::shared_ptr< Kakshya::SoundFileContainer > > m_extracted_audio
std::unordered_map< std::shared_ptr< Kakshya::CameraContainer >, std::shared_ptr< Buffers::VideoContainerBuffer > > m_camera_buffers
std::unordered_map< uint32_t, SpatialCaptureEntry > m_spatial_captures
std::unordered_map< std::string, int > m_spatial_cache_refcounts
Closes+erases a cache at zero.
std::unordered_map< std::shared_ptr< Kakshya::SoundFileContainer >, std::vector< std::shared_ptr< Buffers::SoundContainerBuffer > > > m_audio_buffers
IOManager(const IOManager &)=delete
std::unordered_map< uint32_t, AudioCaptureState > m_audio_captures
std::unordered_map< std::shared_ptr< Kakshya::VideoStreamContainer >, std::shared_ptr< Buffers::VideoContainerBuffer > > m_video_buffers
std::vector< std::shared_ptr< VideoFileWriter > > m_video_writers
std::vector< std::shared_ptr< SoundFileReader > > get_audio_readers() const
Returns all retained audio readers.
Core::GlobalStreamInfo & m_stream_info
std::vector< std::shared_ptr< ImageReader > > get_image_readers() const
Returns all retained image readers.
std::vector< std::shared_ptr< SoundFileWriter > > get_sound_writers() const
Returns all registered SoundFileWriters created via create_writer() or write().
std::vector< std::shared_ptr< SoundFileReader > > m_audio_readers
std::mutex m_volume_captures_mutex
std::vector< std::shared_ptr< Buffers::VolumeGridBuffer > > get_loaded_volumes() const
Returns all VolumeGridBuffers created via load_volume().
IOManager & operator=(IOManager &&)=delete
std::vector< std::shared_ptr< SoundFileWriter > > m_writers
std::mutex m_spatial_captures_mutex
std::shared_mutex m_camera_mutex
std::mutex m_video_captures_mutex
std::vector< std::shared_ptr< ModelReader > > m_model_readers
std::unordered_map< std::string, std::shared_ptr< IO::SpatialCache > > m_spatial_caches
std::vector< std::shared_ptr< Buffers::VolumeGridBuffer > > m_loaded_volumes
IOManager(IOManager &&)=delete
std::shared_mutex m_buffers_mutex
std::shared_ptr< Buffers::BufferManager > m_buffer_manager
std::unordered_map< uint32_t, VideoCaptureState > m_video_captures
std::unordered_map< uint64_t, std::shared_ptr< CameraSource > > m_camera_readers
std::vector< std::shared_ptr< ImageReader > > m_image_readers
std::shared_ptr< Registry::Service::IOService > m_io_service
std::shared_mutex m_readers_mutex
std::mutex m_audio_captures_mutex
std::unordered_map< uint32_t, std::unique_ptr< IO::VolumeCapture > > m_volume_captures
std::unordered_map< uint64_t, std::shared_ptr< VideoFileReader > > m_video_readers
std::vector< std::shared_ptr< ModelReader > > get_model_readers() const
Returns all retained model readers.
std::shared_ptr< Vruta::TaskScheduler > m_scheduler
IOManager & operator=(const IOManager &)=delete
Parallel::AsyncGroup< bool > m_save_tasks
Optional orchestration layer for IO reader lifetime and IOService dispatch.
Shared field description and byte layout for a CompositeArray.
Definition Composite.hpp:52
Owns a set of in-flight std::async futures on behalf of a subsystem.
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.
AudioReadOptions
Audio-specific reading options.
@ DEINTERLEAVE
Output planar (per-channel) doubles instead of interleaved.
FileReadOptions
Generic options for file reading behavior.
@ EXTRACT_METADATA
Extract file metadata.
@ EXTRACT_REGIONS
Extract semantic regions (format-specific)
std::pair< std::shared_ptr< Kakshya::VideoFileContainer >, std::shared_ptr< Kakshya::SoundFileContainer > > VideoLoadResult
Result of load_video(path, config): the video container, then its audio.
Definition IOManager.hpp:90
bool save_mesh_snapshot(const std::shared_ptr< Buffers::ComputeMeshBuffer > &buffer, const std::string &path_pattern, const ModelWriteOptions &options)
Save a ComputeMeshBuffer with a millisecond epoch timestamp spliced into the path.
VideoReadOptions
Video-specific reading options.
std::function< std::shared_ptr< Core::VKImage >(const std::string &)> TextureResolver
Callable that maps a raw material texture path to a GPU image.
Definition Depot.hpp:31
bool save_volume(const std::shared_ptr< Buffers::VolumeGridBuffer > &volume, const std::string &suggested_name)
Present a native save-file dialog filtered to volume formats and save volume to the chosen path via I...
Definition Depot.cpp:503
bool save_image(const std::shared_ptr< Buffers::TextureBuffer > &buffer, const std::string &suggested_name)
Present a native save-file dialog filtered to image formats and save buffer to the chosen path via IO...
Definition Depot.cpp:371
bool save_mesh(const std::shared_ptr< Buffers::MeshBuffer > &buffer, const std::string &suggested_name)
Present a native save-file dialog filtered to 3D model formats and save buffer to the chosen path via...
Definition Depot.cpp:441
bool save_composite(const std::shared_ptr< Kakshya::CompositeContainer > &container, const std::string &suggested_name)
Choose a CSV or TSV destination and save a CompositeContainer.
Definition Depot.cpp:418
Comprehensive configuration for digital audio stream processing.
Configuration for opening a camera device via FFmpeg.
std::shared_ptr< Kakshya::AudioOutputContainer > container
std::shared_ptr< SoundFileWriter > writer
std::unique_ptr< IO::SpatialCapture > capture
std::string filepath
Keys m_spatial_cache_refcounts.
std::shared_ptr< VideoFileWriter > writer
Configuration for model writing.
VideoReadOptions video_options
Definition IOManager.hpp:76
AudioReadOptions audio_options
Definition IOManager.hpp:75
Configuration for volume writing.
A lattice and every field sampled over it, held in host memory.
Backend audio subsystem service interface.