Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Swift support #178

Open
wants to merge 86 commits into
base: experimental-record
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
ab46995
Improving examples build configs and documentation.
jferreyra-sc Dec 15, 2023
fb63bbc
reverted to sdk 30.0.2
jferreyra-sc Dec 15, 2023
0e5bd48
Set min deployment target on iOS 11 for the example xcode project
jferreyra-sc Dec 19, 2023
f2143f8
Added npm instructions and examples server instructions.
jferreyra-sc Dec 19, 2023
fd1b314
Use std::expected for outcome when available
jb-gcx Mar 14, 2024
ecffe7e
Merge pull request #166 from jb-gcx/gcx/use-std-expected
li-feng-sc Apr 4, 2024
cde53fd
Merge remote-tracking branch 'origin/main' into experimental-record
li-feng-sc Apr 10, 2024
5e03b11
swift test
li-feng-sc Apr 16, 2024
3f40e5a
Fix compilation for djinni::Future
jb-gcx May 8, 2024
2e8e231
wip example
li-feng-sc May 16, 2024
1d84790
cpp files
li-feng-sc May 16, 2024
ff0c4e8
remove unneeded dep
li-feng-sc May 16, 2024
47e3ddd
Merge pull request #168 from jb-gcx/gcx/fixup-future
li-feng-sc May 17, 2024
2e8e189
optimize parameter copy
li-feng-sc May 17, 2024
3e3a662
refactor
li-feng-sc May 21, 2024
420ccc2
refactor
li-feng-sc May 21, 2024
1135f3a
Make DJFuture results nonnull in objective C
jb-gcx May 21, 2024
2842302
namespace
li-feng-sc May 22, 2024
5d95c1b
implement proxy caches
li-feng-sc May 24, 2024
8552689
initial codegen
li-feng-sc Jun 4, 2024
cf0fb53
+nc
li-feng-sc Jun 4, 2024
9972b74
wip
li-feng-sc Jun 14, 2024
9a74ee9
Merge pull request #170 from jb-gcx/gcx/nonnull-future
li-feng-sc Jun 26, 2024
36c027e
Use official C++ 20 coroutine feature test macro
jb-gcx Jul 8, 2024
ea62a3b
all swift tests passing
li-feng-sc Jul 11, 2024
bcf8b1c
comments
li-feng-sc Jul 12, 2024
f98a415
skip label for first parameter
li-feng-sc Jul 12, 2024
6f52b82
Fix extern type usage with non-nullable ptrs
trblunt Jul 14, 2024
5858e64
delete test code
li-feng-sc Jul 15, 2024
7b6cfc3
Merge pull request #179 from trblunt/fix-extern-nn-ptrs
li-feng-sc Jul 15, 2024
cc8190a
Merge pull request #155 from jferreyra-sc/main
li-feng-sc Jul 15, 2024
f603002
Allow move-only types in Promise<>. (based on contribution from jb-gcx)
li-feng-sc Jul 15, 2024
4b57d4a
Merge pull request #176 from jb-gcx/gcx/coroutine-feature-test
li-feng-sc Jul 15, 2024
bd74aaa
Merge remote-tracking branch 'origin/main' into lf/swift-support
li-feng-sc Jul 15, 2024
fe7e06e
Make `unexpect` available for djinni expected
jb-gcx Jul 8, 2024
00878de
delete unneeded overload
li-feng-sc Jul 16, 2024
5466945
Merge pull request #180 from Snapchat/lf/move-only-promise
li-feng-sc Jul 16, 2024
fb12c73
Fix continuations in djinni::Future coroutines
jb-gcx Jul 8, 2024
ee2bcb1
Add an XCTest for coroutine cleanup order
jb-gcx Jul 16, 2024
c6c9f07
Fix feature detection for experimental coroutines
jb-gcx Jul 18, 2024
a97afc7
Merge pull request #181 from jb-gcx/gcx/fix-coroutine-continuation
li-feng-sc Jul 18, 2024
ae855ee
add SharedFuture
techleeksnap Jul 19, 2024
74c6a57
Update support-lib/cpp/SharedFuture.hpp
techleeksnap Jul 19, 2024
74cedd6
cleanup
li-feng-sc Jul 22, 2024
513a6be
add support for deriving(hashable, sendable, codable)
li-feng-sc Jul 23, 2024
ffe02b8
add error conformance
li-feng-sc Jul 23, 2024
14d5aa3
refactor
li-feng-sc Jul 23, 2024
ba173f7
Implement swift future
LoganShireSnapchat Jul 24, 2024
25898a5
Merge pull request #185 from LoganShireSnapchat/lshire-swift-future
li-feng-sc Jul 24, 2024
2f0334a
comment
li-feng-sc Jul 24, 2024
9d1a289
fix warning
li-feng-sc Jul 24, 2024
edada37
address comments
techleeksnap Jul 24, 2024
c7cbf0a
use atomic int64 instead of uuid as subscription token
li-feng-sc Jul 24, 2024
1a4080d
fix
techleeksnap Jul 24, 2024
bf60220
address comments
techleeksnap Jul 24, 2024
987cd72
address comments
techleeksnap Jul 24, 2024
823a8eb
add header
li-feng-sc Jul 26, 2024
7494022
Merge branch 'main' into lf/swift-support
li-feng-sc Jul 26, 2024
954a8a9
move-only optionals
li-feng-sc Jul 26, 2024
d29731e
touch
li-feng-sc Jul 26, 2024
8e404d8
test
li-feng-sc Jul 26, 2024
e1a08ff
uncomment
li-feng-sc Jul 26, 2024
0c74a2a
touch
li-feng-sc Jul 26, 2024
c0b131e
try again
li-feng-sc Jul 26, 2024
d3ab313
try again
li-feng-sc Jul 26, 2024
c08793d
comment
li-feng-sc Jul 26, 2024
683c39f
touch
li-feng-sc Jul 26, 2024
ce6add1
Update support-lib/cpp/Future.hpp
techleeksnap Jul 26, 2024
c271198
add prefix dirs
li-feng-sc Jul 31, 2024
b604359
Merge pull request #183 from techleeksnap/sharedfuture
li-feng-sc Aug 1, 2024
6c30c31
Merge remote-tracking branch 'origin/main' into lf/swift-support
li-feng-sc Aug 1, 2024
17458b9
Small error in README.md
ysammy Aug 1, 2024
252271d
fixes as per review comments
li-feng-sc Aug 7, 2024
dc8b666
Finish futures when their promise is broken
jb-gcx Aug 12, 2024
a237529
downgrade swift protobuf to 1.26
li-feng-sc Aug 13, 2024
2383dc1
Merge pull request #186 from ysammy/patch-1
li-feng-sc Aug 14, 2024
971228b
Merge pull request #187 from jb-gcx/gcx/broken-promises
li-feng-sc Aug 14, 2024
9c687a2
Merge branch 'main' into lf/swift-support
li-feng-sc Aug 15, 2024
eedf683
fixes
li-feng-sc Aug 15, 2024
6e986dd
fixes for snap client integration
li-feng-sc Aug 29, 2024
539df09
fixes
li-feng-sc Aug 29, 2024
be7eff9
revert the nonnull future get change because it prevents future<optio…
li-feng-sc Aug 30, 2024
dd172fe
Remove -fcoroutines-ts
ivan-golub Sep 26, 2024
3d3c46b
Remove -fcoroutines-ts from host_cxxopt too
ivan-golub Sep 26, 2024
3fe25d8
Merge pull request #189 from ivan-golub/patch-1
li-feng-sc Sep 26, 2024
82ba611
avoid swift keywords
li-feng-sc Oct 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bazelrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
build --cxxopt=-std=c++17 --host_cxxopt=-std=c++17 --incompatible_java_common_parameters=false --define=android_dexmerger_tool=d8_dexmerger --define=android_incremental_dexing_tool=d8_dexbuilder --nouse_workers_with_dexbuilder
build --cxxopt=-std=c++17 --cxxopt=-fcoroutines-ts --host_cxxopt=-std=c++17 --host_cxxopt=-fcoroutines-ts --incompatible_java_common_parameters=false --define=android_dexmerger_tool=d8_dexmerger --define=android_incremental_dexing_tool=d8_dexbuilder --nouse_workers_with_dexbuilder
9 changes: 5 additions & 4 deletions support-lib/cpp/Future.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,6 @@ class Future {
return true;
}

template<typename ConcretePromise>
struct PromiseTypeBase {
Promise<T> _promise;
std::optional<djinni::expected<T, std::exception_ptr>> _result{};
Expand All @@ -379,7 +378,9 @@ class Future {
constexpr bool await_ready() const noexcept {
return false;
}
bool await_suspend(detail::CoroutineHandle<ConcretePromise> finished) const noexcept {
template <typename P>
bool await_suspend(detail::CoroutineHandle<P> finished) const noexcept {
static_assert(std::is_convertible_v<P*, PromiseTypeBase*>);
auto& promise_type = finished.promise();
if (*promise_type._result) {
if constexpr (std::is_void_v<T>) {
Expand All @@ -406,7 +407,7 @@ class Future {
}
};

struct PromiseType: PromiseTypeBase<PromiseType>{
struct PromiseType: PromiseTypeBase {
template <typename V, typename = std::enable_if_t<std::is_convertible_v<V, T>>>
void return_value(V&& value) {
this->_result.emplace(std::forward<V>(value));
Expand All @@ -424,7 +425,7 @@ class Future {

#if defined(DJINNI_FUTURE_HAS_COROUTINE_SUPPORT)
template<>
struct Future<void>::PromiseType : PromiseTypeBase<PromiseType> {
struct Future<void>::PromiseType : PromiseTypeBase {
void return_void() {
_result.emplace();
}
Expand Down
162 changes: 162 additions & 0 deletions support-lib/cpp/SharedFuture.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/**
* Copyright 2021 Snap, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include "Future.hpp"

#if defined(DJINNI_FUTURE_HAS_COROUTINE_SUPPORT)

#include <memory>
#include <optional>
#include <type_traits>
#include <variant>
#include <vector>

namespace djinni {

// SharedFuture is a wrapper around djinni::Future to allow multiple consumers (i.e. like std::shared_future)
// The API is designed to be similar to djinni::Future.
template<typename T>
class SharedFuture {
public:
// Create SharedFuture from Future. Runtime error if the future is already consumed.
explicit SharedFuture(Future<T>&& future);

// Transform into Future<T>.
Future<T> toFuture() const {
if (await_ready()) {
co_return await_resume(); // return stored value directly
}
co_return co_await SharedFuture(*this); // retain copy during coroutine suspension
}

void wait() const {
waitIgnoringExceptions().wait();
}

decltype(auto) get() const {
wait();
return await_resume();
}

template <typename Func>
using ResultT = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<Func, const SharedFuture<T>&>>>;

// Transform the result of this future into a new future. The behavior is same as Future::then except that
// it doesn't consume the future, and can be called multiple times.
template<typename Func>
Future<ResultT<Func>> then(Func transform) const {
auto cpy = SharedFuture(*this); // retain copy during coroutine suspension
co_await cpy.waitIgnoringExceptions();
co_return transform(cpy);
}

// Same as above but returns SharedFuture.
template<typename Func>
SharedFuture<ResultT<Func>> thenShared(Func transform) const {
return SharedFuture<ResultT<Func>>(then(std::move(transform)));
}

// -- coroutine support implementation only; not intended externally --

bool await_ready() const {
std::scoped_lock lock(_sharedStates->mutex);
return _sharedStates->storedValue.has_value();
}

decltype(auto) await_resume() const {
if (!*_sharedStates->storedValue) {
std::rethrow_exception(_sharedStates->storedValue->error());
}
if constexpr (!std::is_void_v<T>) {
return const_cast<const T &>(_sharedStates->storedValue->value());
}
}

bool await_suspend(detail::CoroutineHandle<> h) const;

struct Promise : public Future<T>::promise_type {
SharedFuture<T> get_return_object() noexcept {
return SharedFuture(Future<T>::promise_type::get_return_object());
}
};
using promise_type = Promise;

private:
Future<void> waitIgnoringExceptions() const {
try {
co_await *this;
} catch (...) {
// Ignore exceptions.
}
}

struct SharedStates {
std::recursive_mutex mutex;
std::optional<djinni::expected<T, std::exception_ptr>> storedValue = std::nullopt;
std::vector<detail::CoroutineHandle<>> coroutineHandles;
};
// Use a shared_ptr to allow copying SharedFuture.
std::shared_ptr<SharedStates> _sharedStates = std::make_shared<SharedStates>();
};

// CTAD deduction guide to construct from Future directly.
template<typename T>
SharedFuture(Future<T>&&) -> SharedFuture<T>;

// ------------------ Implementation ------------------

template<typename T>
SharedFuture<T>::SharedFuture(Future<T>&& future) {
// `future` will invoke all continuations when it is ready.
future.then([sharedStates = _sharedStates](auto futureResult) {
std::vector toCall = [&] {
std::scoped_lock lock(sharedStates->mutex);
try {
if constexpr (std::is_void_v<T>) {
futureResult.get();
sharedStates->storedValue.emplace();
} else {
sharedStates->storedValue = futureResult.get();
}
} catch (...) {
sharedStates->storedValue = make_unexpected(std::current_exception());
}
return std::move(sharedStates->coroutineHandles);
}();
for (auto& handle : toCall) {
handle();
}
});
}

template<typename T>
bool SharedFuture<T>::await_suspend(detail::CoroutineHandle<> h) const {
{
std::unique_lock lock(_sharedStates->mutex);
if (!_sharedStates->storedValue) {
_sharedStates->coroutineHandles.push_back(std::move(h));
return true;
}
}
h();
return true;
}

} // namespace djinni

#endif
1 change: 1 addition & 0 deletions test-suite/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ objc_library(
copts = [
"-ObjC++",
"-std=c++17",
"-fcoroutines-ts"
],
srcs = glob([
"generated-src/objc/**/*.mm",
Expand Down
91 changes: 91 additions & 0 deletions test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>

#include "../../../support-lib/cpp/SharedFuture.hpp"

@interface DBSharedFutureTest : XCTestCase
@end

@implementation DBSharedFutureTest

#ifdef DJINNI_FUTURE_HAS_COROUTINE_SUPPORT

- (void)setUp
{
[super setUp];
}

- (void)tearDown
{
[super tearDown];
}

- (void)testCreateFuture
{
djinni::SharedFuture<int> resolvedInt(djinni::Promise<int>::resolve(42));
XCTAssertEqual(resolvedInt.get(), 42);

djinni::Promise<NSString*> strPromise;
djinni::SharedFuture futureString(strPromise.getFuture());

strPromise.setValue(@"foo");
XCTAssertEqualObjects(futureString.get(), @"foo");
}

- (void)testThen
{
djinni::Promise<int> intPromise;
djinni::SharedFuture<int> futureInt(intPromise.getFuture());

auto transformedInt = futureInt.thenShared([](const auto& resolved) { return 2 * resolved.get(); });

intPromise.setValue(42);
XCTAssertEqual(transformedInt.get(), 84);

// Also verify multiple consumers and chaining.
auto transformedString = futureInt.thenShared([](const auto& resolved) { return std::to_string(resolved.get()); });
auto futurePlusOneTimesTwo = futureInt.then([](auto resolved) { return resolved.get() + 1; }).then([](auto resolved) {
return 2 * resolved.get();
});
auto futureStringLen = transformedString.then([](auto resolved) { return resolved.get().length(); });

XCTAssertEqual(transformedString.get(), std::string("42"));
XCTAssertEqual(futurePlusOneTimesTwo.get(), (42 + 1) * 2);
XCTAssertEqual(futureStringLen.get(), 2);

XCTAssertEqual(futureInt.get(), 42);

auto voidFuture = transformedString.thenShared([](auto) {});
voidFuture.wait();

auto intFuture2 = voidFuture.thenShared([](auto) { return 43; });
XCTAssertEqual(intFuture2.get(), 43);
}

- (void)testException
{
// Also verify exception handling.
djinni::Promise<int> intPromise;
djinni::SharedFuture<int> futureInt(intPromise.getFuture());

intPromise.setException(std::runtime_error("mocked"));

XCTAssertThrows(futureInt.get());

auto thenResult = futureInt.then([](auto resolved) { return resolved.get(); });
XCTAssertThrows(thenResult.get());

auto withExceptionHandling = futureInt.thenShared([](const auto& resolved) {
try {
return resolved.get();
} catch (...) {
return -1;
}
});
withExceptionHandling.wait();
XCTAssertEqual(withExceptionHandling.get(), -1);
}

#endif

@end
Loading