A Frame is a fundamental data structure in τ (tau). It acts as a container, grouping together one or more Schedules. Think of it as the primary unit of composition and storage. When you query τ, you are interacting with Frames.
Each Frame is assigned a unique, time-sortable identifier (a ULID) upon creation, ensuring that every Frame can be uniquely referenced.
The Structure of a Frame
A Frame is composed of two main components:
id: A unique[]const u8identifier for the Frame.schedules: An array ofSchedulestructs.
This structure allows a single Frame to hold multiple, independent timelines of data, which can be queried and reasoned about collectively.
1/// A frame represents a collection of schedules identified by a unique id.
2pub const Frame = struct {
3 id: []const u8,
4 schedules: []Schedule,
5 // ... methods
6};Core Operations
Frames are designed to be append-only, ensuring data immutability and a clear audit trail.
1. Creating a Frame
You can create a new Frame by providing an allocator and an initial slice of Schedules. The create function will automatically generate a new ULID for the frame and perform a deep copy of the provided schedules.
1const std = @import("std");
2const tau = @import("libtau");
3
4const allocator = std.heap.page_allocator;
5
6// 1. Create a Tau (the actual data point)
7const my_tau = try tau.primitives.Tau.create(allocator, 42.0, 0, 1000);
8defer my_tau.deinit(allocator);
9
10// 2. Create a Schedule containing the Tau
11const my_schedule = try tau.primitives.Schedule.create(allocator, "sensor_a", &[_]tau.primitives.Tau{my_tau});
12defer my_schedule.deinit(allocator);
13
14// 3. Create a Frame containing the Schedule
15const my_frame = try tau.primitives.Frame.create(allocator, &[_]tau.primitives.Schedule{my_schedule});
16defer my_frame.deinit(allocator);
17
18std.debug.print("Created Frame with ID: {s}\n", .{my_frame.id});A Frame cannot be empty. The create function asserts that you must provide at least one Schedule.
2. Adding Schedules to a Frame
The addSchedules function allows you to append new Schedules to an existing Frame. This operation is also append-only and performs a deep copy of the new schedules, preserving the immutability of the original data.
1// ... assuming my_frame exists from the previous example
2
3// Create a second schedule
4const new_tau = try tau.primitives.Tau.create(allocator, -15.5, 500, 1500);
5defer new_tau.deinit(allocator);
6const new_schedule = try tau.primitives.Schedule.create(allocator, "sensor_b", &[_]tau.primitives.Tau{new_tau});
7defer new_schedule.deinit(allocator);
8
9// Add the new schedule to the frame
10try my_frame.addSchedules(allocator, &[_]tau.primitives.Schedule{new_schedule});
11
12std.debug.print("Frame now contains {} schedules.\n", .{my_frame.schedules.len}); // Output: 2Serialization and Deserialization
Frames can be serialized into a compact binary format for storage or network transmission. The serialize and deserialize methods provide an efficient way to manage the lifecycle of a Frame.
The binary format is structured as follows:
id_len(u32, big-endian)id([]u8)schedules_count(u32, big-endian)- For each schedule:
a.
schedule_bytes_len(u32, big-endian) b.schedule_bytes([]u8)
This simple, explicit format ensures that Frames can be read and written efficiently by any part of the τ ecosystem.