Shapes example¶
This example illustrates how to manage interests on objects using C++.
The idea is:
- You publish some shapes on a Sen bus.
- Shapes move around, emit events and change color.
- In another process, you instantiate a
ShapeListener. - Using a Sen shell, you tell the listener to start/stop listening to shapes using some criteria.
- You will see how the listener is automatically informed by Sen on the objects that match the given criteria.
Interface¶
This is the data model:
package shapes;
/// Centimeters encoded in 32-bit floats.
quantity<f32, cm> Centimeters;
/// Degrees encoded in 32-bit floats.
quantity<f32, deg> Degrees;
/// A color enumeration.
enum Color : u8 { red, green, blue }
// Specification of a triangle.
struct Triangle { side : Centimeters }
// Specification of a rectangle.
struct Rectangle { width : Centimeters, height : Centimeters }
// Specification of a circle.
struct Circle { radius : Centimeters }
// A 2-D 32 bit vector
struct Vec2 { x : Centimeters, y : Centimeters }
// The geometry of a shape.
variant Geometry { Triangle, Rectangle, Circle }
// The wall that we might collide with.
enum Wall : u8 { top, bottom, left, right }
// A shape.
class Shape
{
var geometry : Geometry [static]; // Shape's geometry.
var frozen : bool [writable]; // True if the shape should move.
var color : Color [writable]; // Shape's color.
var position : Vec2; // Shape's center.
// Emitted when the shape collides with a wall.
//
// @param wall the wall that we collided with.
event collidedWithWall(wall : Wall);
}
import "shapes.stl"
package shapes;
// An distance interval
struct DistanceInterval
{
min : Centimeters,
max : Centimeters
}
// A color or nothing
optional<Color> MaybeColor;
// An interval or nothing
optional<DistanceInterval> MaybeInterval;
// A list of names
sequence<string> NameList;
// Base class for listening to various shapes
class ShapeListener
{
// Number of shapes being tracked
var detectedShapesCount: u32;
// Number of active queries
var queryCount: u32;
// Start listening for shapes that fulfil certain conditions.
// Returns the name of the query that was created.
//
// @param bus the bus where to find the shapes.
// @param color if present, the color that the shape must have.
// @param xRange if present, the interval along the x axis where the shape should be.
// @param yRange if present, the interval along the y axis where the shape should be.
fn startListeningTo(bus : string, color : MaybeColor, xRange : MaybeInterval, yRange : MaybeInterval) -> string;
// Deletes a previously created query.
//
// @param queryName the name of the query (the one returned by calls to startListeningTo)
fn stopListeningTo(queryName : string);
// Deletes all previously created queries.
fn stopListening();
// Get the names of all the detected shapes
fn getAllDetectedShapes() -> NameList;
}
Implementation¶
The shape implementation you can find the shapes.cpp file defines the update() function. In it, we just
move it around, making it bounce in a box. The shape emits a collidedWith(wall) event when it collides with a wall.
The more interesting part is in shape_listener.cpp. Let's see how the startListening function is implemented:
std::string startListeningToImpl(const std::string& bus,
const MaybeColor& color,
const MaybeInterval& xRange,
const MaybeInterval& yRange) override
{
const auto query = makeQueryName(); // create a name for our query
auto sub = std::make_shared<sen::Subscription<ShapeInterface>>(); // create up a subscription
sub->source = api_->getSource(bus); // get the bus
// install the callbacks
std::ignore = sub->list.onAdded([query, this](const auto& iterators) { shapesDetected(query, iterators); });
std::ignore = sub->list.onRemoved([query, this](const auto& iterators) { shapesGone(query, iterators); });
auto interest = makeInterest(query, bus, color, xRange, yRange); // build the interest.
sub->source->addSubscriber(interest, &sub->list, true); // connect the list.
subscriptions_.emplace(query, std::move(sub)); // save the subscription.
// update the query count
setNextQueryCount(subscriptions_.size());
return query;
}
The makeInterest() function builds a Sen Query Language string adapted to the conditions defined by the user, for
example:
SELECT shapes.Shape FROM my.tutorial
WHERE position.x BETWEEN 1.0 AND 2.0 AND
position.y BETWEEN 0.0 AND 3.0 AND
color IN (green, blue)
The listener prints the shapes that is able to detect.
How to run it¶
Let's define what we want to run in our Sen kernel.
# $schema: ../base/schema.json
include:
- ../base/ether.yaml
- ../base/shell.yaml
build:
- name: shapes_producer
group: 4
freqHz: 5
imports: [shapes]
objects:
- class: shapes.ShapeImpl
name: blueCircle
bus: my.tutorial
color: blue
x: 50.0
y: 50.0
geometry:
type: Circle
value:
radius: 10
- class: shapes.ShapeImpl
name: redTriangle
bus: my.tutorial
color: red
x: 15.0
y: 22.0
geometry:
type: Triangle
value:
side: 4
- class: shapes.ShapeImpl
name: greenRectangle
bus: my.tutorial
color: green
x: 40.0
y: 40.0
geometry:
type: Rectangle
value:
width: 20
height: 10
# $schema: ../base/schema.json
include:
- ../base/ether.yaml
build:
- name: shapes_listener
group: 4
freqHz: 5
imports: [shapes]
objects:
- class: shapes.ShapeListenerImpl
name: listener
bus: my.tutorial
Listening to all shapes¶
In one terminal (let's call it A):
In another terminal (let's call it B):
In another terminal (let's call it C):
In terminal C, type:
In terminal A you will see:
- The query that was built for expressing the interest.
- The shapes that the listener is now detecting.
- Logs that are printed when the detected shapes emit some events.
If you now type the following in terminal C:
In terminal A now you see that we don't get any new notification.
Filtering by color¶
Now, try the same as before, but specify a color:
Filtering by location¶
In both dimensions:
my.tutorial.listener.startListeningTo "my.tutorial", null, {"min": 0, "max": 20}, {"min": 0, "max": 20}