There are some tests to check if it builds correctly and they can be disabled with BUILD_TESTS OFF.
You can create a file like cmake/installLibrary.cmake and put in there:
include(FetchContent)
FetchContent_Declare(
lunaris-future
GIT_REPOSITORY https://github.com/LunarisLib/lunaris-future.git
GIT_TAG (put version here)
)
FetchContent_MakeAvailable(lunaris-future)This will allow you to download and link the library like:
# ...
include(cmake/installLibrary.cmake) # does the FetchContent
target_link_libraries(YourProjectName PRIVATE
lunaris::lunaris-future
)If you get the install version with the lib and headers and want to avoid recompiling the library yourself, you can do
# ...
find_package(lunaris-future REQUIRED)
target_link_libraries(YourProjectName PRIVATE
lunaris::lunaris-future
)The find_package will try to find the lunaris-future-config.cmake or similar files that should be available to download in the Release tab.
Future is a needed feature in C++ for asynchronous tasks. This tries to perfect exactly that.
Standard std::future lack the then() part and the get() holding the value for as long as you need. You can get() if you want to move the stored value, but that's a choice, and that's what's great about it.
The promise and futures here are all valid for as long as they exist. This means you can actually use it as a tunnel between threads like messages of a specific type all the time.
This can be accomplished by doing get() and waiting for the next value. It won't hold more than one value at a time, so it's not exactly designed for that, but it can work (last example).
using namespace Lunaris::Future;
Promise<int> prom;
Future<int> nxt = prom.get_future()
.then([](int val) { std::cout << "Thenn!: " << val << "\n"; return val * 2; })
.then([](int val) { std::cout << "Then now return void, but got: " << val << "\n"; return; })
.then([]() { std::cout << "Then void to void.\n"; return; })
.then([]() { std::cout << "Then got void, but returning 10 again\n"; return 10; });
std::thread thr([&] {
nxt.wait();
std::cout << "Got value, wait 1 sec...\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Value set: " << nxt.get() << std::endl;
});
std::cout << "Wait...\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
prom.set(64);
thr.join();
return 0;A promise of type int can create a future of its type. A future holds information about a value that is (probably) not set yet.
From a future of a type T you can add a function tied to it via then(). The function will replace any get() from it, and it is executed exactly when (and where) the promise (or future, if it's then() of then()) is set.
Using a function you can then get a future for the return of it, like, [](T a){/*do smth and return type X;*/} will create a future of type X.
The code above uses all the possibilites: type to type, type to void, void to void and void to type. This proves that any type will work.
- A
promiseof typeintis created. This is used by your async task to set the value in the future. - After that, we get the
futureusingget_future(), and from thatfutureof typeintwe hook a function that handles that value directly, showingThenn!: <number>and returningval * 2. It's afutureofintagain (return fromthen()) because of that return. - The next
futureis set with anotherthen()directly, printing another message and returning nothing. That'svoid. Afutureofvoidis created after that. - That
futureis then set with a function that gets nothing (void) and returns nothing too, anotherfutureofvoid. - Lastly we have a function that gets nothing (
void) and returns anint. Afutureofintis created. - The thread
thrwaits for thenxt(lastfutureofint) to be set. It will be only set after all the otherfutures, in sequence, so that means all thosefutures withthen()run and set in sequence one after another. - After one second, the main thread runs
set(64), triggering thefutures in sequence. All functions are called in sequence exactly there and the last value is set for the lastfuture,nxt. - The thread
thrdetects the value and counts 1 second before showing the last value,10.
Wait...
Thenn!: 64
Then now return void, but got: 128
Then void to void.
Then got void, but returning 10 again
Got value, wait 1 sec...
Value set: 10
You can actually call the promise as many times as you want. This enables it to be used in some weird ways.
using namespace Lunaris;
Promise<int> pa, pb;
Future<int> fa, fb;
fa = pa.get_future();
fb = pb.get_future();
std::thread a([&] {
pa.set(0); // start
while (fb.get_copy() < 10) { // checks copy of value only
std::cout << "ThreadA: " << fb.get_copy() << std::endl; // print copy of value
pa.set(fb.get() + 1); // takes value off here
}
pa.set(10);
});
std::thread b([&] {
while (fa.get_copy() < 10) { // same logic
std::cout << "ThreadB: " << fa.get_copy() << std::endl;
pb.set(fa.get() + 1);
}
pb.set(10);
});
a.join();
b.join();
return 0;This works by design, not because it was expected to be used that way. "You can do it" doesn't mean you should, but I'll show it just so I can say that this works.
Two promises, two futures, two threads. One thread starts with set() and waits the second via get_copy(), then the second gets the value set and set that value + 1. The get() ones reset the internal set values and makes the while wait the other thread set the next value on and on.
This goes until one of them gets 10, then both set 10 for convenience and exit. This is a counter using two threads and "infinite" promises.
What this means is that you can have a kind of tunnel between two tasks and maybe use that to ask something and wait it back without recreating the futures and promises, or maybe you want a fancy combo of functions tied to one argument and trigger that as many times as you want. This can be useful, but at the same time this is not fast at all (wait, set, wait, get, ...).