|
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
SomeClassmay 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, theClientclass, 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 forClientfor more details.) libkazv offers a check when it is compiled with-DCMAKE_BUILD_TYPE=Debug. This checks if the current thread calling a method ofClientis the same thread that thisClientbelongs 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 aClientinto 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.