455 lines
15 KiB
C++
455 lines
15 KiB
C++
#include <CesiumAsync/AsyncSystem.h>
|
|
#include <CesiumAsync/Future.h>
|
|
#include <CesiumAsync/IAssetAccessor.h>
|
|
#include <CesiumAsync/IAssetRequest.h>
|
|
#include <CesiumAsync/IAssetResponse.h>
|
|
#include <CesiumAsync/ITaskProcessor.h>
|
|
#include <CesiumAsync/Promise.h>
|
|
#include <CesiumGltf/Model.h>
|
|
#include <CesiumNativeTests/SimpleAssetAccessor.h>
|
|
#include <CesiumNativeTests/SimpleAssetRequest.h>
|
|
#include <CesiumNativeTests/SimpleAssetResponse.h>
|
|
|
|
#include <doctest/doctest.h>
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using namespace CesiumNativeTests;
|
|
|
|
namespace {
|
|
|
|
//! [simplest-task-processor]
|
|
class SimplestTaskProcessor : public CesiumAsync::ITaskProcessor {
|
|
public:
|
|
void startTask(std::function<void()> f) override {
|
|
std::thread(std::move(f)).detach();
|
|
}
|
|
};
|
|
//! [simplest-task-processor]
|
|
|
|
//! [async-system-singleton]
|
|
CesiumAsync::AsyncSystem& getAsyncSystem() {
|
|
static CesiumAsync::AsyncSystem asyncSystem(
|
|
std::make_shared<SimplestTaskProcessor>());
|
|
return asyncSystem;
|
|
}
|
|
//! [async-system-singleton]
|
|
|
|
std::shared_ptr<CesiumAsync::IAssetAccessor> getAssetAccessor() {
|
|
static std::shared_ptr<CesiumAsync::IAssetAccessor> pAssetAccessor =
|
|
std::make_shared<SimpleAssetAccessor>(
|
|
std::map<std::string, std::shared_ptr<SimpleAssetRequest>>{
|
|
{"https://example.com",
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
"https://example.com",
|
|
CesiumAsync::HttpHeaders(),
|
|
std::make_unique<SimpleAssetResponse>(
|
|
uint16_t(200),
|
|
"text/html",
|
|
CesiumAsync::HttpHeaders(),
|
|
std::vector<std::byte>()))},
|
|
{"http://example.com/image.png",
|
|
std::make_shared<SimpleAssetRequest>(
|
|
"GET",
|
|
"https://example.com",
|
|
CesiumAsync::HttpHeaders(),
|
|
std::make_unique<SimpleAssetResponse>(
|
|
uint16_t(200),
|
|
"image.png",
|
|
CesiumAsync::HttpHeaders(),
|
|
std::vector<std::byte>()))}});
|
|
return pAssetAccessor;
|
|
}
|
|
|
|
struct ProcessedContent {
|
|
static ProcessedContent createFailed(const std::string& message) {
|
|
return ProcessedContent{message};
|
|
}
|
|
|
|
std::optional<std::string> failureMessage;
|
|
|
|
bool isFailed() const { return failureMessage.has_value(); }
|
|
std::string getFailureMessage() const {
|
|
return failureMessage ? *failureMessage : "";
|
|
}
|
|
};
|
|
|
|
ProcessedContent
|
|
processDownloadedContent(const std::span<const std::byte>& /*bytes*/) {
|
|
return ProcessedContent();
|
|
}
|
|
void useDownloadedContent(const std::span<const std::byte>& /*bytes*/) {}
|
|
void updateApplicationWithProcessedContent(
|
|
const ProcessedContent& /*content*/) {}
|
|
|
|
CesiumAsync::Future<ProcessedContent>
|
|
startOperationThatMightFail(const CesiumAsync::AsyncSystem& asyncSystem) {
|
|
return asyncSystem.createResolvedFuture(ProcessedContent());
|
|
}
|
|
|
|
void showError(const std::string& /*message*/) {}
|
|
|
|
std::string
|
|
findReferencedImageUrl(const std::span<const std::byte>& /*bytes*/) {
|
|
return "http://example.com/image.png";
|
|
}
|
|
|
|
std::string findReferencedImageUrl(const ProcessedContent& /*processed*/) {
|
|
return "http://example.com/image.png";
|
|
}
|
|
|
|
std::vector<std::string>
|
|
findReferencedImageUrls(const ProcessedContent& /*processed*/) {
|
|
return {"http://example.com/image.png"};
|
|
}
|
|
|
|
void useLoadedImage(const std::shared_ptr<CesiumAsync::IAssetRequest>&) {}
|
|
|
|
struct SlowValue {};
|
|
void computeSomethingSlowly(
|
|
const std::string& /*parameter*/,
|
|
std::function<void(const SlowValue&)> f) {
|
|
f(SlowValue());
|
|
}
|
|
|
|
template <typename T> void doSomething(const T&) {}
|
|
|
|
//! [compute-something-slowly-wrapper]
|
|
CesiumAsync::Future<SlowValue> myComputeSomethingSlowlyWrapper(
|
|
const CesiumAsync::AsyncSystem& asyncSystem,
|
|
const std::string& someParameter) {
|
|
CesiumAsync::Promise<SlowValue> promise =
|
|
asyncSystem.createPromise<SlowValue>();
|
|
|
|
computeSomethingSlowly(someParameter, [promise](const SlowValue& value) {
|
|
promise.resolve(value);
|
|
});
|
|
|
|
return promise.getFuture();
|
|
}
|
|
//! [compute-something-slowly-wrapper]
|
|
|
|
//! [compute-something-slowly-wrapper-handle-exception]
|
|
CesiumAsync::Future<SlowValue> myComputeSomethingSlowlyWrapper2(
|
|
const CesiumAsync::AsyncSystem& asyncSystem,
|
|
const std::string& someParameter) {
|
|
return asyncSystem.createFuture<SlowValue>(
|
|
[&](const CesiumAsync::Promise<SlowValue>& promise) {
|
|
computeSomethingSlowly(
|
|
someParameter,
|
|
[promise](const SlowValue& value) { promise.resolve(value); });
|
|
});
|
|
}
|
|
//! [compute-something-slowly-wrapper-handle-exception]
|
|
|
|
CesiumGltf::Model getModelFromSomewhere() { return {}; }
|
|
|
|
void giveBackModel(CesiumGltf::Model&&) {}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("AsyncSystem Examples") {
|
|
//! [create-async-system]
|
|
CesiumAsync::AsyncSystem asyncSystem(
|
|
std::make_shared<SimplestTaskProcessor>());
|
|
//! [create-async-system]
|
|
|
|
//! [capture-by-value]
|
|
auto someLambda = [asyncSystem]() {
|
|
return asyncSystem.createResolvedFuture(4);
|
|
};
|
|
//! [capture-by-value]
|
|
|
|
SUBCASE("wait") {
|
|
//! [create-request-future]
|
|
std::shared_ptr<CesiumAsync::IAssetAccessor> pAssetAccessor =
|
|
getAssetAccessor();
|
|
|
|
CesiumAsync::Future<std::shared_ptr<CesiumAsync::IAssetRequest>> future =
|
|
pAssetAccessor->get(asyncSystem, "https://example.com");
|
|
//! [create-request-future]
|
|
|
|
//! [wait]
|
|
std::shared_ptr<CesiumAsync::IAssetRequest> pCompletedRequest =
|
|
std::move(future).wait();
|
|
//! [wait]
|
|
}
|
|
|
|
SUBCASE("thenInMainThread") {
|
|
std::shared_ptr<CesiumAsync::IAssetAccessor> pAssetAccessor =
|
|
getAssetAccessor();
|
|
|
|
//! [continuation]
|
|
CesiumAsync::Future<std::shared_ptr<CesiumAsync::IAssetRequest>>
|
|
requestFuture = pAssetAccessor->get(asyncSystem, "https://example.com");
|
|
|
|
CesiumAsync::Future<void> future =
|
|
std::move(requestFuture)
|
|
.thenInMainThread(
|
|
[](std::shared_ptr<CesiumAsync::IAssetRequest>&& pRequest) {
|
|
const CesiumAsync::IAssetResponse* pResponse =
|
|
pRequest->response();
|
|
// handling of an error response ommitted
|
|
useDownloadedContent(pResponse->data());
|
|
});
|
|
//! [continuation]
|
|
|
|
std::move(future).waitInMainThread();
|
|
}
|
|
|
|
SUBCASE("chaining") {
|
|
std::shared_ptr<CesiumAsync::IAssetAccessor> pAssetAccessor =
|
|
getAssetAccessor();
|
|
|
|
//! [chaining]
|
|
CesiumAsync::Future<void> future =
|
|
pAssetAccessor->get(asyncSystem, "https://example.com")
|
|
.thenInWorkerThread(
|
|
[](std::shared_ptr<CesiumAsync::IAssetRequest>&& pRequest) {
|
|
const CesiumAsync::IAssetResponse* pResponse =
|
|
pRequest->response();
|
|
// handling of an error response ommitted
|
|
ProcessedContent processed =
|
|
processDownloadedContent(pResponse->data());
|
|
return processed;
|
|
})
|
|
.thenInMainThread([](ProcessedContent&& processed) {
|
|
updateApplicationWithProcessedContent(processed);
|
|
});
|
|
//! [chaining]
|
|
|
|
std::move(future).waitInMainThread();
|
|
}
|
|
|
|
SUBCASE("catch") {
|
|
//! [catch]
|
|
CesiumAsync::Future<void> future =
|
|
startOperationThatMightFail(asyncSystem)
|
|
.catchImmediately([](std::exception&& e) {
|
|
return ProcessedContent::createFailed(e.what());
|
|
})
|
|
.thenInMainThread([](ProcessedContent&& processed) {
|
|
if (processed.isFailed()) {
|
|
showError(processed.getFailureMessage());
|
|
} else {
|
|
updateApplicationWithProcessedContent(processed);
|
|
}
|
|
});
|
|
//! [catch]
|
|
|
|
std::move(future).waitInMainThread();
|
|
}
|
|
|
|
SUBCASE("unwrapping") {
|
|
std::shared_ptr<CesiumAsync::IAssetAccessor> pAssetAccessor =
|
|
getAssetAccessor();
|
|
|
|
//! [unwrapping]
|
|
CesiumAsync::Future<void> future =
|
|
pAssetAccessor->get(asyncSystem, "https://example.com")
|
|
.thenInWorkerThread(
|
|
[pAssetAccessor, asyncSystem](
|
|
std::shared_ptr<CesiumAsync::IAssetRequest>&& pRequest) {
|
|
const CesiumAsync::IAssetResponse* pResponse =
|
|
pRequest->response();
|
|
// handling of an error response ommitted
|
|
std::string url = findReferencedImageUrl(pResponse->data());
|
|
return pAssetAccessor->get(asyncSystem, url);
|
|
})
|
|
.thenInMainThread([](std::shared_ptr<CesiumAsync::IAssetRequest>&&
|
|
pImageRequest) {
|
|
// Do something with the loaded image
|
|
useLoadedImage(pImageRequest);
|
|
});
|
|
//! [unwrapping]
|
|
|
|
std::move(future).waitInMainThread();
|
|
}
|
|
|
|
SUBCASE("then-pass-through") {
|
|
std::shared_ptr<CesiumAsync::IAssetAccessor> pAssetAccessor =
|
|
getAssetAccessor();
|
|
|
|
//! [then-pass-through]
|
|
CesiumAsync::Future<void> future =
|
|
pAssetAccessor->get(asyncSystem, "https://example.com")
|
|
.thenInWorkerThread(
|
|
[pAssetAccessor, asyncSystem](
|
|
std::shared_ptr<CesiumAsync::IAssetRequest>&& pRequest) {
|
|
const CesiumAsync::IAssetResponse* pResponse =
|
|
pRequest->response();
|
|
|
|
// handling of an error response ommitted
|
|
|
|
ProcessedContent processed =
|
|
processDownloadedContent(pResponse->data());
|
|
std::string url = findReferencedImageUrl(processed);
|
|
return pAssetAccessor->get(asyncSystem, url)
|
|
.thenPassThrough(std::move(processed));
|
|
})
|
|
.thenInMainThread(
|
|
[](std::tuple<
|
|
ProcessedContent,
|
|
std::shared_ptr<CesiumAsync::IAssetRequest>>&& tuple) {
|
|
auto& [processed, pImageRequest] = tuple;
|
|
useLoadedImage(pImageRequest);
|
|
updateApplicationWithProcessedContent(processed);
|
|
});
|
|
//! [then-pass-through]
|
|
|
|
std::move(future).waitInMainThread();
|
|
}
|
|
|
|
SUBCASE("all") {
|
|
std::shared_ptr<CesiumAsync::IAssetAccessor> pAssetAccessor =
|
|
getAssetAccessor();
|
|
|
|
//! [all]
|
|
CesiumAsync::Future<void> future =
|
|
pAssetAccessor->get(asyncSystem, "https://example.com")
|
|
.thenInWorkerThread(
|
|
[pAssetAccessor, asyncSystem](
|
|
std::shared_ptr<CesiumAsync::IAssetRequest>&& pRequest) {
|
|
const CesiumAsync::IAssetResponse* pResponse =
|
|
pRequest->response();
|
|
|
|
// handling of an error response ommitted
|
|
|
|
ProcessedContent processed =
|
|
processDownloadedContent(pResponse->data());
|
|
|
|
std::vector<std::string> urls =
|
|
findReferencedImageUrls(processed);
|
|
|
|
std::vector<CesiumAsync::Future<
|
|
std::shared_ptr<CesiumAsync::IAssetRequest>>>
|
|
futures;
|
|
futures.reserve(urls.size());
|
|
|
|
for (const std::string& url : urls) {
|
|
futures.emplace_back(pAssetAccessor->get(asyncSystem, url));
|
|
}
|
|
|
|
return asyncSystem.all(std::move(futures))
|
|
.thenPassThrough(std::move(processed));
|
|
})
|
|
.thenInMainThread(
|
|
[](std::tuple<
|
|
ProcessedContent,
|
|
std::vector<std::shared_ptr<CesiumAsync::IAssetRequest>>>&&
|
|
tuple) {
|
|
auto& [processed, imageRequests] = tuple;
|
|
|
|
for (const std::shared_ptr<CesiumAsync::IAssetRequest>&
|
|
pImageRequest : imageRequests) {
|
|
useLoadedImage(pImageRequest);
|
|
}
|
|
|
|
updateApplicationWithProcessedContent(processed);
|
|
});
|
|
//! [all]
|
|
|
|
std::move(future).waitInMainThread();
|
|
}
|
|
|
|
SUBCASE("create-resolved-future") {
|
|
std::shared_ptr<CesiumAsync::IAssetAccessor> pAssetAccessor =
|
|
getAssetAccessor();
|
|
|
|
//! [create-resolved-future]
|
|
CesiumAsync::Future<void> future =
|
|
pAssetAccessor->get(asyncSystem, "https://example.com")
|
|
.thenInWorkerThread(
|
|
[pAssetAccessor, asyncSystem](
|
|
std::shared_ptr<CesiumAsync::IAssetRequest>&& pRequest) {
|
|
const CesiumAsync::IAssetResponse* pResponse =
|
|
pRequest->response();
|
|
|
|
// handling of an error response ommitted
|
|
|
|
ProcessedContent processed =
|
|
processDownloadedContent(pResponse->data());
|
|
std::optional<std::string> maybeUrl =
|
|
findReferencedImageUrl(processed);
|
|
if (!maybeUrl) {
|
|
return asyncSystem.createResolvedFuture<
|
|
std::shared_ptr<CesiumAsync::IAssetRequest>>(nullptr);
|
|
} else {
|
|
return pAssetAccessor->get(asyncSystem, *maybeUrl);
|
|
}
|
|
})
|
|
.thenInMainThread([](std::shared_ptr<CesiumAsync::IAssetRequest>&&
|
|
pImageRequest) {
|
|
if (pImageRequest) {
|
|
useLoadedImage(pImageRequest);
|
|
}
|
|
});
|
|
//! [create-resolved-future]
|
|
|
|
std::move(future).waitInMainThread();
|
|
}
|
|
|
|
SUBCASE("promise") {
|
|
//! [compute-something-slowly]
|
|
computeSomethingSlowly("some parameter", [](const SlowValue& value) {
|
|
doSomething(value);
|
|
});
|
|
//! [compute-something-slowly]
|
|
|
|
//! [compute-something-slowly-async-system]
|
|
CesiumAsync::Promise<SlowValue> promise =
|
|
asyncSystem.createPromise<SlowValue>();
|
|
|
|
computeSomethingSlowly("some parameter", [promise](const SlowValue& value) {
|
|
promise.resolve(value);
|
|
});
|
|
|
|
CesiumAsync::Future<void> future =
|
|
promise.getFuture().thenInMainThread([](SlowValue&& value) {
|
|
// Continue working with the slowly-computed value in the main thread.
|
|
doSomething(value);
|
|
});
|
|
//! [compute-something-slowly-async-system]
|
|
|
|
future.waitInMainThread();
|
|
}
|
|
|
|
SUBCASE("lambda-move") {
|
|
//! [lambda-move]
|
|
CesiumGltf::Model model = getModelFromSomewhere();
|
|
CesiumAsync::Future<void> future =
|
|
asyncSystem
|
|
.runInWorkerThread([model = std::move(model)]() mutable {
|
|
doSomething(model);
|
|
return std::move(model);
|
|
})
|
|
.thenInMainThread([](CesiumGltf::Model&& model) {
|
|
giveBackModel(std::move(model));
|
|
});
|
|
//! [lambda-move]
|
|
|
|
future.waitInMainThread();
|
|
}
|
|
|
|
SUBCASE("use example functions") {
|
|
CesiumAsync::AsyncSystem& localAsyncSystem = getAsyncSystem();
|
|
myComputeSomethingSlowlyWrapper(localAsyncSystem, "something")
|
|
.waitInMainThread();
|
|
myComputeSomethingSlowlyWrapper2(localAsyncSystem, "something")
|
|
.waitInMainThread();
|
|
}
|
|
}
|