libcopp 2.3.1
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
stackful_channel.h
Go to the documentation of this file.
1// Copyright 2025 owent
2
3#pragma once
4
6#include <libcopp/utils/config/libcopp_build_features.h>
7
9
10// clang-format off
11#include <libcopp/utils/config/stl_include_prefix.h> // NOLINT(build/include_order)
12// clang-format on
13
14#include <functional>
15#include <type_traits>
16#include <unordered_set>
17
18#if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
19# include <exception>
20#endif
21
22#if defined(LIBCOPP_MACRO_ENABLE_STD_VARIANT) && LIBCOPP_MACRO_ENABLE_STD_VARIANT
23# include <variant>
24#endif
25
26#ifdef __cpp_impl_three_way_comparison
27# include <compare>
28#endif
29
30// clang-format off
31#include <libcopp/utils/config/stl_include_suffix.h> // NOLINT(build/include_order)
32// clang-format on
33
36
37LIBCOPP_COPP_NAMESPACE_BEGIN
38
41#if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
42class coroutine_context_fiber;
43#endif
44
46
47template <class TCONTEXT>
49
50template <class TCOROUTINE_OBJECT>
52
53template <class TVALUE>
54class LIBCOPP_COPP_API_HEAD_ONLY stackful_channel_context;
55
56template <class TVALUE>
57class LIBCOPP_COPP_API_HEAD_ONLY stackful_channel_receiver;
58
59template <class TVALUE>
60class LIBCOPP_COPP_API_HEAD_ONLY stackful_channel_sender;
61
62template <>
64 LIBCOPP_COPP_API static int resume(void *invoke_ctx, stackful_channel_context_base *priv_data);
65};
66
67#if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
68template <>
69struct stackful_channel_resume_handle<coroutine_context_fiber> {
70 LIBCOPP_COPP_API static int resume(void *invoke_ctx, stackful_channel_context_base *priv_data);
71};
72#endif
73
74template <class TCOROUTINE_OBJECT>
78 if (nullptr != invoke_ctx) {
79 return static_cast<TCOROUTINE_OBJECT *>(invoke_ctx)->resume(reinterpret_cast<void *>(priv_data));
80 }
81
82 return 0;
83 }
84};
85
86template <class TCOROUTINE_OBJECT>
88 static_assert(::std::is_base_of<coroutine_context_base, TCOROUTINE_OBJECT>::value,
89 "TCOROUTINE_OBJECT must be coroutine_context_base or it's derived class");
90
91 LIBCOPP_COPP_API_HEAD_ONLY inline static int resume(void *invoke_ctx, stackful_channel_context_base *priv_data) {
93 reinterpret_cast<coroutine_context_base *>(invoke_ctx), priv_data);
94 }
95};
96
97struct LIBCOPP_COPP_API_HEAD_ONLY stackful_channel_handle_delegate {
99 int (*resume_handle)(void *, stackful_channel_context_base *priv_data);
100
101 LIBCOPP_UTIL_FORCEINLINE stackful_channel_handle_delegate() noexcept : handle_data(nullptr), resume_handle(nullptr) {}
102
104 : handle_data(other.handle_data), resume_handle(other.resume_handle) {}
105
107 : handle_data(other.handle_data), resume_handle(other.resume_handle) {
108 other.handle_data = nullptr;
109 other.resume_handle = nullptr;
110 }
111
113 const stackful_channel_handle_delegate &other) noexcept {
114 handle_data = other.handle_data;
115 resume_handle = other.resume_handle;
116 return *this;
117 }
118
120 stackful_channel_handle_delegate &&other) noexcept {
121 handle_data = other.handle_data;
122 resume_handle = other.resume_handle;
123
124 other.handle_data = nullptr;
125 other.resume_handle = nullptr;
126 return *this;
127 }
128
129 template <class TCOROUTINE_OBJECT>
130 explicit inline stackful_channel_handle_delegate(TCOROUTINE_OBJECT *ctx) noexcept
131 : handle_data{reinterpret_cast<void *>(ctx)}, resume_handle{nullptr} {
132 if (handle_data != nullptr) {
134 }
135 }
136
138 : handle_data{nullptr}, resume_handle{nullptr} {}
139
141 const stackful_channel_handle_delegate &r) noexcept {
142 return l.handle_data == r.handle_data;
143 }
144#ifdef __cpp_impl_three_way_comparison
145 LIBCOPP_UTIL_FORCEINLINE friend auto operator<=>(const stackful_channel_handle_delegate &l,
146 const stackful_channel_handle_delegate &r) noexcept {
147 return l.handle_data <=> r.handle_data;
148 }
149#else
151 const stackful_channel_handle_delegate &r) noexcept {
152 return l.handle_data != r.handle_data;
153 }
155 const stackful_channel_handle_delegate &r) noexcept {
156 return l.handle_data < r.handle_data;
157 }
159 const stackful_channel_handle_delegate &r) noexcept {
160 return l.handle_data <= r.handle_data;
161 }
163 const stackful_channel_handle_delegate &r) noexcept {
164 return l.handle_data > r.handle_data;
165 }
167 const stackful_channel_handle_delegate &r) noexcept {
168 return l.handle_data >= r.handle_data;
169 }
170#endif
171 LIBCOPP_UTIL_FORCEINLINE operator bool() const noexcept { return !!handle_data; }
172
173 template <class TCOROUTINE_OBJECT>
175 handle_data = reinterpret_cast<void *>(ctx);
176 if (handle_data != nullptr) {
178 }
179
180 return *this;
181 }
182
184 handle_data = nullptr;
185 resume_handle = nullptr;
186 return *this;
187 }
188};
189
190// hash for stackful_channel_handle_delegate
191struct LIBCOPP_COPP_API_HEAD_ONLY stackful_channel_handle_delegate_hash {
193 return std::hash<void *>()(stackful_channel_handle_delegate.handle_data);
194 }
195};
196
198 public:
201
202 private:
207
208 protected:
209 LIBCOPP_COPP_API stackful_channel_context_base() noexcept;
210 LIBCOPP_COPP_API ~stackful_channel_context_base();
211
212 public:
213 LIBCOPP_COPP_API void add_caller(handle_delegate handle) noexcept;
214
215 LIBCOPP_COPP_API bool remove_caller(handle_delegate handle) noexcept;
216
217 LIBCOPP_COPP_API size_t resume_callers();
218
219 LIBCOPP_COPP_API bool has_multiple_callers() const noexcept;
220
221 protected:
222 LIBCOPP_COPP_API void wake();
223
224 private:
226#if defined(LIBCOPP_MACRO_ENABLE_STD_VARIANT) && LIBCOPP_MACRO_ENABLE_STD_VARIANT
227 std::variant<handle_delegate, multi_caller_set> callers_;
228#else
230 // Mostly, there is only one caller for a promise, we needn't hash map to store one handle
231 std::unique_ptr<multi_caller_set> multiple_callers_;
232#endif
233};
234
235template <class TVALUE>
236class LIBCOPP_COPP_API_HEAD_ONLY stackful_channel_context : public stackful_channel_context_base {
237 public:
238 using value_type = TVALUE;
241
242 public:
244 std::is_nothrow_constructible<future::future<value_type>>::value) {}
245
247 std::is_nothrow_destructible<future::future<value_type>>::value) {}
248
249 public:
250 LIBCOPP_UTIL_FORCEINLINE bool is_ready() const noexcept { return data_.is_ready(); }
251
252 LIBCOPP_UTIL_FORCEINLINE bool is_pending() const noexcept { return data_.is_pending(); }
253
254 LIBCOPP_UTIL_FORCEINLINE void reset_value() noexcept(noexcept(std::declval<future::future<TVALUE>>().reset_data())) {
255 data_.reset_data();
256 }
257
258 template <class U>
259 LIBCOPP_UTIL_FORCEINLINE void set_value(U &&in) noexcept(
260 noexcept(std::declval<future::future<TVALUE>>().reset_data(std::forward<U>(in)))) {
261 data_.reset_data(std::forward<U>(in));
262
263 // Wakeup all waiting coroutines
264 resume_callers();
265 }
266
267 LIBCOPP_UTIL_FORCEINLINE const value_type *get_value() const noexcept { return data_.data(); }
268
269 LIBCOPP_UTIL_FORCEINLINE value_type *get_value() noexcept { return data_.data(); }
270
271 template <
272 class TCONTEXT, class TERROR_TRANSFORM,
273 class = nostd::enable_if_t<!std::is_base_of<coroutine_context, nostd::remove_cvref_t<TCONTEXT>>::value
274#if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
275 && !std::is_base_of<coroutine_context_fiber, nostd::remove_cvref_t<TCONTEXT>>::value
276#endif
277 >>
278 LIBCOPP_UTIL_FORCEINLINE value_type inject_await(TCONTEXT *ctx, TERROR_TRANSFORM &&error_transform) noexcept(
279 std::is_nothrow_copy_constructible<value_type>::value && noexcept(error_transform(COPP_EC_ARGS_ERROR))) {
280 return internal_inject_await<TCONTEXT>(ctx, std::forward<TERROR_TRANSFORM>(error_transform));
281 }
282
283 template <class TERROR_TRANSFORM>
284 LIBCOPP_UTIL_FORCEINLINE value_type inject_await(coroutine_context *ctx, TERROR_TRANSFORM &&error_transform) noexcept(
285 std::is_nothrow_copy_constructible<value_type>::value && noexcept(error_transform(COPP_EC_ARGS_ERROR))) {
286 return internal_inject_await<coroutine_context>(ctx, std::forward<TERROR_TRANSFORM>(error_transform));
287 }
288
289#if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
290 template <class TERROR_TRANSFORM>
291 LIBCOPP_UTIL_FORCEINLINE value_type
292 inject_await(coroutine_context_fiber *ctx,
293 TERROR_TRANSFORM &&error_transform) noexcept(std::is_nothrow_copy_constructible<value_type>::value &&
294 noexcept(error_transform(COPP_EC_ARGS_ERROR))) {
295 return internal_inject_await<coroutine_context_fiber>(ctx, std::forward<TERROR_TRANSFORM>(error_transform));
296 }
297#endif
298
299 private:
300 template <class TCONTEXT, class TERROR_TRANSFORM>
301 inline value_type internal_inject_await(TCONTEXT *ctx, TERROR_TRANSFORM &&error_transform) noexcept(
302 std::is_nothrow_copy_constructible<value_type>::value && noexcept(error_transform(COPP_EC_ARGS_ERROR))) {
303 if (is_ready()) {
304 return *get_value();
305 }
306
307 if LIBCOPP_UTIL_UNLIKELY_CONDITION (nullptr == ctx) {
308 return error_transform(COPP_EC_ARGS_ERROR);
309 }
310
311 add_caller(handle_delegate{ctx});
312 void *check_ptr = nullptr;
313 int res = ctx->yield(&check_ptr);
314 remove_caller(handle_delegate{ctx});
315
316 if (COPP_EC_SUCCESS != res) {
317 return error_transform(static_cast<copp_error_code>(res));
318 }
319
320 // This means the context is not resume by the sender of this context/receiver
321 // Maybe the context is killed
322 if (check_ptr != reinterpret_cast<void *>(this)) {
323 return error_transform(COPP_EC_OPERATION_CANCLE);
324 }
325
326 if LIBCOPP_UTIL_LIKELY_CONDITION (is_ready()) {
327 return *get_value();
328 }
329
330 return error_transform(COPP_EC_NOT_READY);
331 }
332
333 private:
335};
336
337template <class TVALUE>
338class LIBCOPP_COPP_API_HEAD_ONLY stackful_channel_receiver {
339 public:
341 using context_pointer_type = LIBCOPP_COPP_NAMESPACE_ID::memory::default_strong_rc_ptr<context_type>;
343
344 public:
346 : context_(LIBCOPP_COPP_NAMESPACE_ID::memory::default_make_strong<context_type>()) {}
347
348 LIBCOPP_UTIL_FORCEINLINE bool is_ready() const noexcept {
349 if LIBCOPP_UTIL_UNLIKELY_CONDITION (!context_) {
350 return false;
351 }
352 return context_->is_ready();
353 }
354
355 LIBCOPP_UTIL_FORCEINLINE bool is_pending() const noexcept {
356 if LIBCOPP_UTIL_UNLIKELY_CONDITION (!context_) {
357 return false;
358 }
359 return context_->is_pending();
360 }
361
362 LIBCOPP_UTIL_FORCEINLINE void reset_value() noexcept(noexcept(std::declval<context_type>().reset_value())) {
363 if LIBCOPP_UTIL_UNLIKELY_CONDITION (!context_) {
364 return;
365 }
366
367 context_->reset_value();
368 }
369
371 if LIBCOPP_UTIL_UNLIKELY_CONDITION (!context_) {
372 return nullptr;
373 }
374
375 return context_->get_value();
376 }
377
379 if LIBCOPP_UTIL_UNLIKELY_CONDITION (!context_) {
380 return nullptr;
381 }
382
383 return context_->get_value();
384 }
385
386 template <class TCONTEXT, class TERROR_TRANSFORM>
387 LIBCOPP_UTIL_FORCEINLINE value_type inject_await(TCONTEXT *ctx, TERROR_TRANSFORM &&error_transform) noexcept(
388 std::is_nothrow_copy_constructible<value_type>::value && noexcept(error_transform(COPP_EC_ARGS_ERROR))) {
389 if LIBCOPP_UTIL_UNLIKELY_CONDITION (!context_) {
390 return error_transform(COPP_EC_NOT_INITED);
391 }
392
393 return context_->inject_await(ctx, std::forward<TERROR_TRANSFORM>(error_transform));
394 }
395
396 LIBCOPP_UTIL_FORCEINLINE const context_pointer_type &get_context() const noexcept { return context_; }
397
399
400 private:
402};
403
404template <class TVALUE>
405struct LIBCOPP_COPP_API_HEAD_ONLY stackful_inject_awaitable<stackful_channel_receiver<TVALUE>> : ::std::true_type {};
406
407template <class TVALUE>
408class LIBCOPP_COPP_API_HEAD_ONLY stackful_channel_sender {
409 public:
411 using context_pointer_type = LIBCOPP_COPP_NAMESPACE_ID::memory::default_strong_rc_ptr<context_type>;
413
414 public:
415 inline stackful_channel_sender(const context_pointer_type &context) noexcept : context_{context} {}
416
417 LIBCOPP_UTIL_FORCEINLINE bool is_ready() const noexcept {
418 if LIBCOPP_UTIL_UNLIKELY_CONDITION (!context_) {
419 return false;
420 }
421 return context_->is_ready();
422 }
423
424 LIBCOPP_UTIL_FORCEINLINE bool is_pending() const noexcept {
425 if LIBCOPP_UTIL_UNLIKELY_CONDITION (!context_) {
426 return false;
427 }
428 return context_->is_pending();
429 }
430
431 LIBCOPP_UTIL_FORCEINLINE void reset_value() noexcept(noexcept(std::declval<context_type>().reset_value())) {
432 if LIBCOPP_UTIL_UNLIKELY_CONDITION (!context_) {
433 return;
434 }
435
436 context_->reset_value();
437 }
438
439 template <class U>
440 LIBCOPP_UTIL_FORCEINLINE void set_value(U &&in) noexcept(
441 noexcept(std::declval<context_type>().set_value(std::forward<U>(in)))) {
442 if LIBCOPP_UTIL_UNLIKELY_CONDITION (!context_) {
443 return;
444 }
445
446 context_->set_value(std::forward<U>(in));
447 }
448
449 LIBCOPP_UTIL_FORCEINLINE const context_pointer_type &get_context() const noexcept { return context_; }
450
452
453 private:
455};
456
457template <class TVALUE>
458inline std::pair<stackful_channel_receiver<TVALUE>, stackful_channel_sender<TVALUE>> make_stackful_channel() {
461 return std::make_pair(std::move(receiver), std::move(sender));
462}
463
464LIBCOPP_COPP_NAMESPACE_END
base type of all coroutine context
base type of all stackful coroutine context
LIBCOPP_COPP_API bool remove_caller(handle_delegate handle) noexcept
stackful_channel_handle_delegate handle_delegate
LIBCOPP_COPP_API stackful_channel_context_base() noexcept
LIBCOPP_COPP_API bool has_multiple_callers() const noexcept
stackful_channel_context_base & operator=(stackful_channel_context_base &&)=delete
stackful_channel_handle_delegate_hash handle_delegate_hash
std::unordered_set< handle_delegate, handle_delegate_hash > multi_caller_set
LIBCOPP_COPP_API void add_caller(handle_delegate handle) noexcept
stackful_channel_context_base & operator=(const stackful_channel_context_base &)=delete
std::unique_ptr< multi_caller_set > multiple_callers_
stackful_channel_context_base(stackful_channel_context_base &&)=delete
LIBCOPP_COPP_API size_t resume_callers()
stackful_channel_context_base(const stackful_channel_context_base &)=delete
future::future< TVALUE > data_
LIBCOPP_UTIL_FORCEINLINE value_type inject_await(coroutine_context *ctx, TERROR_TRANSFORM &&error_transform) noexcept(std::is_nothrow_copy_constructible< value_type >::value &&noexcept(error_transform(COPP_EC_ARGS_ERROR)))
LIBCOPP_UTIL_FORCEINLINE ~stackful_channel_context() noexcept(std::is_nothrow_destructible< future::future< value_type > >::value)
LIBCOPP_UTIL_FORCEINLINE value_type * get_value() noexcept
LIBCOPP_UTIL_FORCEINLINE bool is_ready() const noexcept
value_type internal_inject_await(TCONTEXT *ctx, TERROR_TRANSFORM &&error_transform) noexcept(std::is_nothrow_copy_constructible< value_type >::value &&noexcept(error_transform(COPP_EC_ARGS_ERROR)))
LIBCOPP_UTIL_FORCEINLINE bool is_pending() const noexcept
LIBCOPP_UTIL_FORCEINLINE stackful_channel_context() noexcept(std::is_nothrow_constructible< future::future< value_type > >::value)
LIBCOPP_UTIL_FORCEINLINE value_type inject_await(TCONTEXT *ctx, TERROR_TRANSFORM &&error_transform) noexcept(std::is_nothrow_copy_constructible< value_type >::value &&noexcept(error_transform(COPP_EC_ARGS_ERROR)))
LIBCOPP_UTIL_FORCEINLINE void reset_value() noexcept(noexcept(std::declval< future::future< TVALUE > >().reset_data()))
LIBCOPP_UTIL_FORCEINLINE const value_type * get_value() const noexcept
LIBCOPP_UTIL_FORCEINLINE void set_value(U &&in) noexcept(noexcept(std::declval< future::future< TVALUE > >().reset_data(std::forward< U >(in))))
LIBCOPP_UTIL_FORCEINLINE context_pointer_type & get_context() noexcept
LIBCOPP_UTIL_FORCEINLINE bool is_ready() const noexcept
context_pointer_type context_
LIBCOPP_UTIL_FORCEINLINE void reset_value() noexcept(noexcept(std::declval< context_type >().reset_value()))
LIBCOPP_UTIL_FORCEINLINE value_type * get_value() noexcept
typename context_type::value_type value_type
LIBCOPP_UTIL_FORCEINLINE bool is_pending() const noexcept
LIBCOPP_UTIL_FORCEINLINE value_type inject_await(TCONTEXT *ctx, TERROR_TRANSFORM &&error_transform) noexcept(std::is_nothrow_copy_constructible< value_type >::value &&noexcept(error_transform(COPP_EC_ARGS_ERROR)))
LIBCOPP_UTIL_FORCEINLINE const context_pointer_type & get_context() const noexcept
LIBCOPP_UTIL_FORCEINLINE const value_type * get_value() const noexcept
LIBCOPP_COPP_NAMESPACE_ID::memory::default_strong_rc_ptr< context_type > context_pointer_type
LIBCOPP_UTIL_FORCEINLINE void set_value(U &&in) noexcept(noexcept(std::declval< context_type >().set_value(std::forward< U >(in))))
LIBCOPP_UTIL_FORCEINLINE bool is_ready() const noexcept
typename context_type::value_type value_type
context_pointer_type context_
stackful_channel_sender(const context_pointer_type &context) noexcept
LIBCOPP_COPP_NAMESPACE_ID::memory::default_strong_rc_ptr< context_type > context_pointer_type
LIBCOPP_UTIL_FORCEINLINE bool is_pending() const noexcept
LIBCOPP_UTIL_FORCEINLINE const context_pointer_type & get_context() const noexcept
LIBCOPP_UTIL_FORCEINLINE void reset_value() noexcept(noexcept(std::declval< context_type >().reset_value()))
LIBCOPP_UTIL_FORCEINLINE context_pointer_type & get_context() noexcept
#define LIBCOPP_UTIL_FORCEINLINE
#define LIBCOPP_UTIL_LIKELY_CONDITION(__C)
#define LIBCOPP_UTIL_UNLIKELY_CONDITION(__C)
copp_error_code
Definition errno.h:11
@ COPP_EC_OPERATION_CANCLE
COPP_EC_OPERATION_CANCLE.
Definition errno.h:33
@ COPP_EC_SUCCESS
COPP_EC_SUCCESS.
Definition errno.h:12
@ COPP_EC_NOT_READY
COPP_EC_NOT_READY.
Definition errno.h:24
@ COPP_EC_NOT_INITED
COPP_EC_NOT_INITED.
Definition errno.h:21
@ COPP_EC_ARGS_ERROR
COPP_EC_ARGS_ERROR.
Definition errno.h:30
STL namespace.
std::pair< stackful_channel_receiver< TVALUE >, stackful_channel_sender< TVALUE > > make_stackful_channel()
size_t operator()(const stackful_channel_handle_delegate &stackful_channel_handle_delegate) const noexcept
LIBCOPP_UTIL_FORCEINLINE stackful_channel_handle_delegate(stackful_channel_handle_delegate &&other) noexcept
LIBCOPP_UTIL_FORCEINLINE stackful_channel_handle_delegate & operator=(std::nullptr_t) noexcept
LIBCOPP_UTIL_FORCEINLINE friend bool operator<(const stackful_channel_handle_delegate &l, const stackful_channel_handle_delegate &r) noexcept
LIBCOPP_UTIL_FORCEINLINE stackful_channel_handle_delegate() noexcept
LIBCOPP_UTIL_FORCEINLINE stackful_channel_handle_delegate & operator=(const stackful_channel_handle_delegate &other) noexcept
LIBCOPP_UTIL_FORCEINLINE friend bool operator<=(const stackful_channel_handle_delegate &l, const stackful_channel_handle_delegate &r) noexcept
LIBCOPP_UTIL_FORCEINLINE stackful_channel_handle_delegate(std::nullptr_t) noexcept
LIBCOPP_UTIL_FORCEINLINE stackful_channel_handle_delegate & operator=(TCOROUTINE_OBJECT *ctx) noexcept
LIBCOPP_UTIL_FORCEINLINE stackful_channel_handle_delegate & operator=(stackful_channel_handle_delegate &&other) noexcept
LIBCOPP_UTIL_FORCEINLINE friend bool operator>(const stackful_channel_handle_delegate &l, const stackful_channel_handle_delegate &r) noexcept
stackful_channel_handle_delegate(TCOROUTINE_OBJECT *ctx) noexcept
LIBCOPP_UTIL_FORCEINLINE friend bool operator==(const stackful_channel_handle_delegate &l, const stackful_channel_handle_delegate &r) noexcept
LIBCOPP_UTIL_FORCEINLINE stackful_channel_handle_delegate(const stackful_channel_handle_delegate &other) noexcept
LIBCOPP_UTIL_FORCEINLINE friend bool operator>=(const stackful_channel_handle_delegate &l, const stackful_channel_handle_delegate &r) noexcept
LIBCOPP_UTIL_FORCEINLINE friend bool operator!=(const stackful_channel_handle_delegate &l, const stackful_channel_handle_delegate &r) noexcept
static LIBCOPP_COPP_API_HEAD_ONLY int resume(void *invoke_ctx, stackful_channel_context_base *priv_data)
static LIBCOPP_UTIL_FORCEINLINE int resume(coroutine_context_base *invoke_ctx, stackful_channel_context_base *priv_data)