-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(guide): make a guide to the records feature
- Loading branch information
Showing
7 changed files
with
665 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.