Understanding the generated codeΒΆ
In the previous example we defined 4 properties, 3 methods and 2 events. Those are there just to illustrate the options, but they will do for this tutorial.
The generated code for this class will result in a base class that will have:
- Getters and setters for the properties.
- Pure virtual functions for the 3 methods (that we will need to implement).
- Protected functions for firing the 2 events.
Let's have a look at the generated file (remember that files generated by Sen end in .stl.h).
Starting with the methods.
class MyClassBase: public sen::NativeObject, public MyClassInterface
{
protected: // MyClass methods
virtual i32 addNumbersImpl(i32 a, i32 b) = 0;
virtual std::string echoImpl(const std::string& message) = 0;
virtual void changePropsImpl() = 0;
};
You see that the signature of the methods that we need to implement is in line with what we defined in the STL file. Sen deals with wrapping these calls in asynchronous functions (that's the reason why they are protected).
If we take a look at the functions related to events:
public: // MyClass event handlers
ConnectionGuard onSomethingHappened(EventCallback<>&& callback) final;
ConnectionGuard onSomethingElseHappened(EventCallback<i32>&& callback) final;
public: // MyClass events emission functions
inline void somethingHappened(Emit mode = Emit::onCommit);
inline void somethingElseHappened(i32 arg, Emit mode = Emit::onCommit);
};
First, we have the functions that clients can use to receive notifications about the emission of
events. Those are named like onXYX(), where XYZ is the name of the event. They receive an
EventCallback<>, which is just a function where clients will get called to. The template
parameters are the types of the arguments of the event (that's where we get the signature of the
callback we expect to receive).
Second, we have the functions we can use to fire those events. They are named after the events. The
mode argument is rarely used but tells Sen when would you like to produce the event.
The functions related to our properties are the following:
// prop1
const std::string& getProp1() const noexcept final;
// prop2
const basic_package::StructOfInts& getProp2() const noexcept final;
void setNextProp2(const basic_package::StructOfInts& val) final;
ConnectionGuard onProp2Changed(PropertyCallback&& callback) final;
// prop3
const basic_package::MyVariant& getProp3() const noexcept final;
void setNextProp3(const basic_package::MyVariant& val) final;
ConnectionGuard onProp3Changed(PropertyCallback&& callback) final;
// prop4
const basic_package::Vec2& getProp4() const noexcept final;
ConnectionGuard onProp4Changed(PropertyCallback&& callback) final;
protected: // MyClass protected properties
void setNextProp4(const basic_package::Vec2& val);
There are getters for all of them. Getters are named like getXYZ(), where XYZ is the name of the
property.
There are also hooks for detecting changes. Those are the ones named like onXYZChanged(). They all
receive a callback to notify clients. You can see that those functions return a ConnectionGuard.
ConnectionGuards keep your callback installed in the object.
We also have the setters. Setters are public when the property is writeable, and protected
otherwise. They are name like setNextXYZ(), where XYZ is the name of the property. The reason
for the "Next" in the name is that the value that we use will only be visible to the world only
after we commit() our changes. This guarantees that the state of objects is consistent in every
cycle, and we only make use of the inputs of the current cycle for computing the next one.
Finally, we have static properties, which are the ones that do not change during the lifetime of our
objects. You need to set them via the constructor to ensure that they always have a valid value. In
our case it is prop1: