Skip to content

Commit

Permalink
feat(guide): make a guide to the records feature
Browse files Browse the repository at this point in the history
  • Loading branch information
brusherru committed Oct 19, 2020
1 parent 708c931 commit 6353e36
Show file tree
Hide file tree
Showing 7 changed files with 665 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ The most straightforward way to extend XOD and add support for new hardware.
- [Creating variadic patch nodes](./creating-variadics/)
- [Creating generic patch nodes](./creating-generics/)
- [Defining custom types](./custom-types/)
- [Creating Data Structures](./records/)
- [Wrapping class-based Arduino libraries](./wrapping-arduino-libraries/)
- [Testing patches](./testing-patches/)

Expand Down
Binary file added docs/guide/records/3d-acceleration.patch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 101 additions & 0 deletions docs/guide/records/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: Creating Data Structures
version: 0.36.0
---

# Creating Data Structures

A data structure is a collection of fields, possibly of different data types. It is represented in C++ as `struct`.

Many of the custom types in XOD are some data packed in the struct. This way, you can quickly pass grouped values through a single terminal. It also allows you to create specializations for generic nodes and thereby implement polymorphism at the XOD language level.

To simplify the packing and unpacking process without coding a C++ implementation manually, the XOD has a particular marker node `xod/patch-nodes/record`. When the user places this node on the patch with some input terminals and `output-self` node, it automatically creates an unpack patch, which can access the struct's values and generates C++ implementations for them.

## Pack the data

Grouping of values is useful when you need to pass a bunch of data in the several nodes without insane linking net all over your patches. This guide will not consider examples of such complex programs but will give examples of how to pack these data and use it where necessary.

Let's imagine that we want to pack outputs from an accelerometer. It has three data values of type Number, which represents acceleration on X, Y, and Z axes. Let's create a record for them.

1. Create a new patch and call it `3d-acceleration`.
2. Place three terminals `xod/patch-nodes/input-number` and give them labels: `X`, `Y`, `Z`.
3. Place the `xod/patch-nodes/record` marker.
4. Place the `xod/patch-nodes/output-self` terminal.

![3d-acceleration record patch](./3d-acceleration.patch.png)

Now this patch represents a struct with three fields of number type and the generated code will looks like:

```cpp
node {
meta {
struct Type {
typeof_X X;
typeof_Y Y;
typeof_Z Z;
};
}

void evaluate(Context ctx) {
Type record = getValue<output_OUT>(ctx);

record._0 = getValue<input_X>(ctx);
record._1 = getValue<input_Y>(ctx);
record._2 = getValue<input_Z>(ctx);

emitValue<output_OUT>(ctx, record);
}
}

```

Also, the `record` marker automatically creates a `@/unpack-3d-acceleration` patch, which you can use to get values from the record without diving in the C++. It generates the code like this:
```cpp
node {
void evaluate(Context ctx) {
auto record = getValue<output_IN>(ctx);

emitValue<output_X>(ctx, record.X);
emitValue<output_Y>(ctx, record.Y);
emitValue<output_Z>(ctx, record.Z);
}
}

```

You can also find out created `@/input-3d-acceleration` and `@/output-3d-acceleration` terminal patches in the project browser. So you can encapsulate some logic over your record inside a new patch. To do so, you need to create a new patch and place the `@/input-3d-acceleration` terminal and the `@/unpack-3d-acceleration` node and work with it as usual if you want to change some values in the record, place a new `@/3d-acceleration` node and link it with `@/output-3d-acceleration`.

## JSON serialization

Records can be serialized to JSON format. JSON is a widespread data interchange format. Most cloud services can work with this format. In this way, records make it easy to send data to various services, including [XOD Cloud Feeds](/docs/guide/getting-started-with-feeds/) and especially [multiple time series](/docs/guide/multiple-time-series/).

The [`xod/json`](https://xod.io/libs/xod/json) standard library has an abstract [`to-json`](https://xod.io/libs/xod/json/to-json) node and specializations for the primitive types. However, when you created a valid record patch, it also automatically creates a specialization for the record, and you can find it in your project with the name `@/to-json(3d-acceleration)`. This patch is a composition of the nodes, and you can open the patch and check out how it works inside. In short, it contains the unpack node, `to-json` node for each field of the record, and concatenate it into a valid JSON using pin labels as keys in a JSON object.

![Send JSON-serialized record to the feeds](./record-to-feeds.patch.png)

## Custom types and nesting

Records can contain any custom types, including other records, without any limitation on the nesting level. It also makes it possible to send complex data structures in JSON format.


![Nested records example](./nested-records.patch.png)

However, most of the custom types have no `to-json` specializations, so you'll get a compilation error if you try to serialize a record, which contains, for example, an `i2c` type. But, if you really need to serialize some custom type you can create `to-json` [specialization](/docs/guide/generics/#specializations) manually. It should output a JSON-serialized string.

## Creating a simple device custom type

Some nodes representing hardware devices and define a custom type can be defined using the same record mechanism instead of implementing it in C++.

However, there are a few limitations:
- device type should not require a custom C++ libraries,
- no need to define custom methods in the type.

For example, the standard library [`xod-dev/ds-rtc`](https://xod.io/libs/xod-dev/ds-rtc) requires an I2C bus and address of the device. These values pack in the records without any problems. We have already replaced a C++ implementation of the custom type with a record marker, and you can check out how it works.

--

1. To make a record type, you need to place the `record` marker node, the `output-self` node, and some input terminals.
2. It automatically creates an unpack patch and `to-json` specialization.
3. Records can contain any types, including other records.
4. Custom types without `to-json` specialization won't let you serialize the entire record. However, you can create a missing specialization.
5. Records make it easy to send JSON serialized data to the feeds or other web services.
Binary file added docs/guide/records/nested-records.patch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/guide/records/record-to-feeds.patch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 6353e36

Please sign in to comment.