libkazv
|
We will assume all of the following:
auto
means in post-c++11 programs. You know the basic input/output in the standard library. You know what a namespace is.By completing this tutorial, we would like you to:
then()
to do another thing after a Promise resolves.If you found you did not accomplish the above after the tutorial, or if you had any questions, please do not hesitate to contact us.
We will use a minimal tutorial to tell how to use libkazv.
The code is in tutorial0.cpp
in this directory.
Use
to build and run the code.
Upon running, you will be asked about your homeserver, username and password. After entering these details (e.g. https://tusooa.xyz
for homeserver, exampleuser
for username and examplepassword
for password), it will try to login with these credentials. If the login fails, it prints an error message and exits. If the login succeeds, it will start syncing indefinitely.
Press Ctrl-c to end the program.
These creates the sdk. io
is boost::asio::io_context
, which we will base our event loop upon. Kazv::CprJobHandler
is a class that actually performs network requests. Kazv::LagerStoreEventEmitter
is a class that emits triggers that you can listen to.
Kazv::makeSdk
puts everything together and gives you an Sdk
object to operate on.
Kazv::SdkModel{}
is the initial state. We now use a default-constructed one.Kazv::AsioPromiseHandler
is a class to assist describing actions in event loops. We will get back to this later.zug::identity
is not yet relevant here.Sdk
is a wrapper class. Most of the real operations are done via Client
. We obtain an instance of it here.
A lot of interesting things here.
client.passwordLogin()
dispatches an action to login with the credentials you entered. It returns a Promise
that resolves when the login is successful, or when there is an error.
A Promise
represents a value that will be available later, and allow you to use it via the then
chaining method. This is achieved by a Promise handler, such as the Kazv::AsioPromiseHandler
we used before. We call a Promise
resolved when the value contained is available. If you have used JavaScript before, this construct may be familiar to you. Note that here the type of the value in the Promise
is fixed: it is always an EffectStatus
. This is due to the limit of the language. The then
method takes a function that can take an EffectStatus
and return either void
, or EffectStatus
, or Promise
.
EffectStatus
has a method named success()
, and you can use it to determine whether the action you dispatched succeeded or not. Here, if it fails, we simply print a failure message and terminate the program. If it succeeds, we call client.startSyncing()
, which will dispatch SyncAction indefinitely, parse the response, and update the state of the sdk. Its return type is also a Promise
(and it resolves when the initial sync finishes), so we can write return client.startSyncing();
instead. However, as there are no more chaining then()
s, it is not very useful to do so.
Note that we use [=]
as the lambda capture, and we do not use the mutable
keyword– Yes, Client
is a copyable type, and every method of Client
is const
, even those that dispatch actions. This is a useful feature enabled by the value oriented design.
A possible pitfall: In an instance method,
[=]
will implicitly capture the current instance by reference, if you use a member in the lambda. Try to avoid using lambdas that captures the current instance by reference in an async callback function (likethen()
):SomeClass::someMethod(){m_client.someOperation().then([=](auto stat) {// Badm_client.someOtherOperation();});}The code above is bad because, it is possible that the instance of
SomeClass
may have been destroyed by the time the callback is actually run. And thus,m_client
, which is acutally(capturedThis)->m_client
, is a dereferencing of a dangling pointer. This will give you a segmentation fault as a result. Also, theClient
class, together with all classes that deals with lager cursors, belongs to some thread. (In general, you should not be calling the methods on an object that does not belong to the current thread, unless it is documented safe to do so. See https://github.com/arximboldi/lager/issues/118 and the documentation forClient
for more details.) libkazv offers a check when it is compiled with-DCMAKE_BUILD_TYPE=Debug
. This checks if the current thread calling a method ofClient
is the same thread that thisClient
belongs to. If not, it will throw an exception. This check will prevent you from accidentally calling some method in a different thread than the expected one.SomeClass::someMethod(){m_client.someOperation().then([client=m_client](auto stat) {// ALSO bad: although this does not suffer from// free-after-use problems, there are thread-safety// problems if someMethod() is not called from// the same thread as the event loop.client.someOtherOperation();});}Instead, you should use the
toEventLoop()
function if you are passing aClient
into an async callback:SomeClass::someMethod(){m_client.someOperation().then([client=toEventLoop()](auto stat) {// Goodclient.someOtherOperation();});}
Start the event loop. Simple. The dispatched actions will not be run till this call.