|
1 | 1 | use crate::{types::*, util::*}; |
2 | 2 | use enumset::EnumSet; |
| 3 | +use godot::classes::RefCounted; |
3 | 4 | use godot::classes::file_access::ModeFlags; |
4 | | -use godot::classes::{RefCounted}; |
5 | 5 | use godot::prelude::*; |
6 | 6 | use godot::tools::GFile; |
7 | 7 | use mcap::records::Metadata; |
8 | 8 | use mcap::write::PrivateRecordOptions; |
9 | 9 | use mcap::{Attachment, Message, Writer, records::MessageHeader}; |
10 | 10 |
|
11 | | - |
12 | | - |
13 | 11 | #[derive(GodotClass)] |
14 | | -/// This class allows writing MCAP files. |
| 12 | +/// MCAP file writer for Godot. |
| 13 | +/// |
| 14 | +/// Overview |
| 15 | +/// - Opens a file and writes MCAP records (channels, schemas, messages, attachments, metadata). |
| 16 | +/// - Accepts either full `MCAPMessage` resources via `write()` or pairs of header+payload via `write_to_known_channel()`. |
| 17 | +/// - Exposes a configurable `options` Resource to control chunking, compression, and emitted indexes before opening. |
| 18 | +/// |
| 19 | +/// Error handling |
| 20 | +/// - Methods returning `bool` yield `false` on failure and set an internal last-error string. |
| 21 | +/// - Use `get_last_error()` to retrieve the message; success clears the last error. |
| 22 | +/// - If the writer is dropped without `close()`, it will attempt to finalize the file in `Drop`. |
| 23 | +/// |
| 24 | +/// Minimal example |
| 25 | +/// ```gdscript |
| 26 | +/// var writer := MCAPWriter.new() |
| 27 | +/// if writer.open("user://test.mcap"): |
| 28 | +/// var ch := MCAPChannel.create("messages") |
| 29 | +/// # Optional: set encoding if needed |
| 30 | +/// # ch.message_encoding = "raw" # e.g. "json" |
| 31 | +/// |
| 32 | +/// var msg := MCAPMessage.create(ch, var_to_bytes_with_objects("Hello World")) |
| 33 | +/// |
| 34 | +/// writer.write(msg) |
| 35 | +/// writer.close() |
| 36 | +/// else: |
| 37 | +/// push_error(writer.get_last_error()) |
| 38 | +/// ``` |
| 39 | +/// |
| 40 | +/// Full example with options, schemas, and channels |
| 41 | +/// ```gdscript |
| 42 | +/// var w := MCAPWriter.new() |
| 43 | +/// # Optional: tune options before opening |
| 44 | +/// w.options = MCAPWriteOptions.new() |
| 45 | +/// w.options.compression = MCAPCompression.None |
| 46 | +/// w.options.emit_summary_offsets = true |
| 47 | +/// w.options.emit_message_indexes = true |
| 48 | +/// w.options.emit_chunk_indexes = true |
| 49 | +/// |
| 50 | +/// if not w.open("user://out.mcap"): |
| 51 | +/// push_error("open failed: %s" % w.get_last_error()) |
| 52 | +/// return |
| 53 | +/// |
| 54 | +/// # Define a schema (optional) |
| 55 | +/// var schema_id := w.add_schema("MyType", "jsonschema", PackedByteArray()) |
| 56 | +/// if schema_id < 0: |
| 57 | +/// push_error("add_schema failed: %s" % w.get_last_error()) |
15 | 58 | /// |
16 | | -/// Error handling: |
17 | | -/// - Methods that return `bool` will return `false` on failure and set an internal last-error message. |
18 | | -/// - Call [`get_last_error()`] to retrieve the most recent error as a `GString`. |
19 | | -/// - On successful operations, the last error is cleared. |
| 59 | +/// # Add a channel |
| 60 | +/// var ch_id := w.add_channel(schema_id, "/ch", "json", {}) |
| 61 | +/// if ch_id < 0: |
| 62 | +/// push_error("add_channel failed: %s" % w.get_last_error()) |
| 63 | +/// |
| 64 | +/// # Write messages to the known channel |
| 65 | +/// var hdr := MCAPMessageHeader.create(ch_id) |
| 66 | +/// hdr.sequence = 1 |
| 67 | +/// hdr.log_time = 1_000_000 # usec |
| 68 | +/// hdr.publish_time = 1_000_000 |
| 69 | +/// var payload := PackedByteArray("{\"hello\":\"world\"}".to_utf8_buffer()) |
| 70 | +/// if not w.write_to_known_channel(hdr, payload): |
| 71 | +/// push_error("write_to_known_channel failed: %s" % w.get_last_error()) |
| 72 | +/// |
| 73 | +/// # Optionally write an attachment or metadata |
| 74 | +/// # var att := MCAPAttachment.create("snapshot.bin", "application/octet-stream", PackedByteArray()) |
| 75 | +/// # w.attach(att) |
| 76 | +/// # var meta := MCAPMetadata.create("run_info", {"key": "value"}) |
| 77 | +/// # w.write_metadata(meta) |
| 78 | +/// |
| 79 | +/// # Ensure chunks end cleanly for streaming readers |
| 80 | +/// w.flush() |
| 81 | +/// |
| 82 | +/// # Finalize the file |
| 83 | +/// if not w.close(): |
| 84 | +/// push_error("close failed: %s" % w.get_last_error()) |
| 85 | +/// ``` |
| 86 | +/// |
| 87 | +/// Notes |
| 88 | +/// - Set or replace `options` before calling `open()`; they’re read once to construct the writer. |
| 89 | +/// - `write()` converts a full MCAPMessage Resource to mcap::Message (includes the channel fields). |
| 90 | +/// - `write_to_known_channel()` avoids schema/channel lookups when you already have their IDs. |
| 91 | +/// - `flush()` finishes the current chunk and flushes I/O to keep the file streamable mid-session. |
| 92 | +/// - Timestamps are microseconds (usec). |
20 | 93 | #[class(init)] |
21 | 94 | struct MCAPWriter { |
22 | 95 | base: Base<RefCounted>, |
@@ -92,8 +165,8 @@ impl MCAPWriter { |
92 | 165 | } |
93 | 166 |
|
94 | 167 | self.path = path; |
95 | | - // reset last error for a fresh session |
96 | | - self.clear_error(); |
| 168 | + // reset last error for a fresh session |
| 169 | + self.clear_error(); |
97 | 170 |
|
98 | 171 | // 1) open file |
99 | 172 | let file = match GFile::open(&self.path, ModeFlags::WRITE) { |
@@ -230,9 +303,10 @@ impl MCAPWriter { |
230 | 303 |
|
231 | 304 | self.with_writer( |
232 | 305 | "write_to_known_channel", |
233 | | - |w| w |
234 | | - .write_to_known_channel(&mcap_header, data.as_slice()) |
235 | | - .map(|_| true), |
| 306 | + |w| { |
| 307 | + w.write_to_known_channel(&mcap_header, data.as_slice()) |
| 308 | + .map(|_| true) |
| 309 | + }, |
236 | 310 | false, |
237 | 311 | ) |
238 | 312 | } |
@@ -260,7 +334,10 @@ impl MCAPWriter { |
260 | 334 |
|
261 | 335 | self.with_writer( |
262 | 336 | "write_private_record", |
263 | | - |w| w.write_private_record(opcode, data.as_slice(), opts).map(|_| true), |
| 337 | + |w| { |
| 338 | + w.write_private_record(opcode, data.as_slice(), opts) |
| 339 | + .map(|_| true) |
| 340 | + }, |
264 | 341 | false, |
265 | 342 | ) |
266 | 343 | } |
@@ -312,7 +389,7 @@ impl MCAPWriter { |
312 | 389 | /// of random data will compress terribly at any chunk size.) |
313 | 390 | #[func] |
314 | 391 | pub fn flush(&mut self) -> bool { |
315 | | - self.with_writer("flush", |w| w.flush().map(|_| true), false) |
| 392 | + self.with_writer("flush", |w| w.flush().map(|_| true), false) |
316 | 393 | } |
317 | 394 |
|
318 | 395 | /// Finalizes and closes the MCAP file. Returns true on success. |
|
0 commit comments