PDI 1.7.0-alpha.2024-05-03

the PDI data interface

ref_any.h
1/*******************************************************************************
2 * Copyright (C) 2015-2023 Commissariat a l'energie atomique et aux energies alternatives (CEA)
3 * Copyright (C) 2020-2021 Institute of Bioorganic Chemistry Polish Academy of Science (PSNC)
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of CEA nor the names of its contributors may be used to
14 * endorse or promote products derived from this software without specific
15 * prior written permission.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 ******************************************************************************/
25
26#ifndef PDI_REF_ANY_H_
27#define PDI_REF_ANY_H_
28
29#include <algorithm>
30#include <cassert>
31#include <functional>
32#include <memory>
33#include <new>
34#include <unordered_map>
35
36#include <pdi/pdi_fwd.h>
37#include <pdi/array_datatype.h>
38#include <pdi/datatype.h>
39#include <pdi/error.h>
40#include <pdi/record_datatype.h>
41#include <pdi/scalar_datatype.h>
42
43namespace PDI {
44
45namespace {
46
49template <bool R, bool W>
50struct Ref_access {
52 using type = void;
53};
54
57template <bool R>
58struct Ref_access<R, true> {
60 using type = void*;
61};
62
65template <>
66struct Ref_access<true, false> {
68 using type = void const *;
69};
70
71template <bool R, bool W>
72using ref_access_t = typename Ref_access<R, W>::type;
73
74} // namespace
75
81class PDI_EXPORT Reference_base
82{
83protected:
88 struct PDI_NO_EXPORT Referenced_buffer {
90 std::function<void()> m_deallocator;
91
94
100
102 std::unordered_map<const Reference_base*, std::function<void(Ref)> > m_notifications;
103
110 Referenced_buffer(std::function<void()> deleter, bool readable, bool writable) noexcept
111 : m_deallocator
112 {
113 deleter
114 }
115 , m_read_locks{readable ? 0 : 1}
116 , m_write_locks{writable ? 0 : 1}
117 {}
118
120
122
124
126 {
127 m_deallocator();
128 assert(m_read_locks == 0 || m_read_locks == 1);
129 assert(m_write_locks == 0 || m_write_locks == 1);
130 assert(m_notifications.empty());
131 }
132 };
133
138 struct PDI_NO_EXPORT Referenced_data {
140 mutable std::shared_ptr<Referenced_buffer> m_buffer;
141
143 void* m_data;
144
147
154 Referenced_data(std::shared_ptr<Referenced_buffer> buffer, void* data, Datatype_sptr type)
155 : m_buffer{std::move(buffer)}
156 , m_data{data}
157 , m_type{std::move(type)}
158 {
159 assert(m_buffer);
160 assert(data);
161 }
162
171 Referenced_data(void* data, std::function<void(void*)> freefunc, Datatype_sptr type, bool readable, bool writable)
172 : m_buffer(std::make_shared<Referenced_buffer>(
173 [data, freefunc, type]()
174 {
175 type->destroy_data(data);
176 freefunc(data);
177 },
178 readable,
179 writable
180 ))
181 , m_data{data}
182 , m_type{type} {
183 assert(data);
184 }
185
186 Referenced_data() = delete;
187
189
191 };
192
195 mutable std::shared_ptr<Referenced_data> m_content;
196
199 static std::shared_ptr<Referenced_data> PDI_NO_EXPORT get_content(const Reference_base& other) noexcept
200 {
201 if (!other.m_content) return nullptr;
202 if (!other.m_content->m_data) return nullptr;
203 return other.m_content;
204 }
205
206 // Symbol should not be exported, but it required to force
207 // generation of all 4 variants of `Ref_any::copy`
208 static Ref do_copy(Ref_r ref);
209
212 Reference_base() noexcept
213 : m_content(nullptr)
214 {}
215
217
219
220 Reference_base& operator= (const Reference_base&) = delete;
221
222 Reference_base& operator= (Reference_base&&) = delete;
223
224public:
227 Datatype_sptr type() const noexcept;
228
229 size_t hash() const noexcept
230 {
231 return std::hash<Referenced_data*>()(get_content(*this).get());
232 }
233
234}; // class Data_ref_base
235
246template <bool R, bool W>
247class PDI_EXPORT Ref_any: public Reference_base
248{
249public:
250 template <bool OR, bool OW>
251 friend class Ref_any;
252
255 Ref_any() = default;
256
263 Ref_any(const Ref_any& other) noexcept
265 {
266 link(get_content(other));
267 }
268
275 template <bool OR, bool OW>
276 Ref_any(const Ref_any<OR, OW>& other) noexcept
278 {
279 link(get_content(other));
280 }
281
285 Ref_any(Ref_any&& other) noexcept
287 {
288 if (!other.m_content) return;
289 // the other ref notification disappears
290 other.m_content->m_buffer->m_notifications.erase(&other);
291 // since we get the same privileges as those we release we can just steal the content
292 m_content = other.m_content;
293 other.m_content = nullptr;
294 }
295
304 Ref_any(void* data, std::function<void(void*)> freefunc, Datatype_sptr type, bool readable, bool writable)
306 {
307 if (type->datasize() && !data && (readable || writable)) {
308 throw Type_error{"Referencing null data with non-null size"};
309 }
310 if (data) {
311 link(std::make_shared<Referenced_data>(data, freefunc, std::move(type), readable, writable));
312 }
313 }
314
318 {
319 reset();
320 }
321
322 Ref_any& operator= (Ref_any&& other) noexcept
323 {
324 // self-copy: nothing to do
325 if (&other == this) return *this;
326 // we'll be copied into, start nullifying ourselves first
327 reset();
328 // if the other is null also, we're done
329 if (other.is_null()) return *this;
330 // the other ref notification disappears
331 other.m_content->m_buffer->m_notifications.erase(&other);
332 // since we get the same privileges as those we release we can just steal the content
333 m_content = other.m_content;
334 other.m_content = nullptr;
335 return *this;
336 }
337
338 Ref_any& operator= (const Ref_any& other) const noexcept
339 {
340 // self-copy: nothing to do
341 if (&other == this) return *this;
342 // we'll be copied into, start nullifying ourselves first
343 reset();
344 // and copy the content from the other
345 link(get_content(other));
346 return *this;
347 }
348
349 bool operator== (const Reference_base& o) const noexcept
350 {
351 is_null();
352 return m_content == get_content(o);
353 }
354
355 bool operator!= (const Reference_base& o) const noexcept
356 {
357 is_null();
358 return m_content != get_content(o);
359 }
360
361 bool operator< (const Reference_base& o) const noexcept
362 {
363 is_null();
364 return m_content < get_content(o);
365 }
366
367 bool operator> (const Reference_base& o) const noexcept
368 {
369 is_null();
370 return m_content > get_content(o);
371 }
372
373 bool operator<= (const Reference_base& o) const noexcept
374 {
375 is_null();
376 return m_content <= get_content(o);
377 }
378
379 bool operator>= (const Reference_base& o) const noexcept
380 {
381 is_null();
382 return m_content >= get_content(o);
383 }
384
390 Ref operator[] (const std::string& member_name) const
391 {
392 return this->operator[] (member_name.c_str());
393 }
394
400 Ref operator[] (const char* member_name) const
401 {
402 if (is_null()) {
403 throw Type_error{"Cannot access member from empty Ref: `{}'", member_name};
404 }
405 std::pair<void*, Datatype_sptr> subref_info = type()->member(member_name, m_content->m_data);
406 Ref result;
407 result.link(std::make_shared<Referenced_data>(m_content->m_buffer, subref_info.first, std::move(subref_info.second)));
408 return result;
409 }
410
416 template <class T>
417 std::enable_if_t<std::is_integral<T>::value, Ref> operator[] (T index) const
418 {
419 if (is_null()) {
420 throw Type_error{"Cannot access array index from empty Ref: `{}'", index};
421 }
422 std::pair<void*, Datatype_sptr> subref_info = type()->index(index, m_content->m_data);
423 Ref result;
424 result.link(std::make_shared<Referenced_data>(m_content->m_buffer, subref_info.first, std::move(subref_info.second)));
425 return result;
426 }
427
433 Ref operator[] (std::pair<std::size_t, std::size_t> slice) const
434 {
435 if (is_null()) {
436 throw Type_error("Cannot access array slice from empty Ref: `{}:{}'", slice.first, slice.second);
437 }
438 std::pair<void*, Datatype_sptr> subref_info = type()->slice(slice.first, slice.second, m_content->m_data);
439 Ref result;
440 result.link(std::make_shared<Referenced_data>(m_content->m_buffer, subref_info.first, std::move(subref_info.second)));
441 return result;
442 }
443
449 {
450 if (is_null()) {
451 throw Type_error{"Cannot dereference an empty Ref"};
452 }
453
454 if (auto&& pointer_type = std::dynamic_pointer_cast<const PDI::Pointer_datatype>(type())) {
455 if constexpr (R) {
456 std::pair<void*, Datatype_sptr> subref_info = type()->dereference(m_content->m_data);
457 Ref result;
458 result.link(std::make_shared<Referenced_data>(m_content->m_buffer, subref_info.first, std::move(subref_info.second)));
459 return result;
460 } else {
461 return Ref_r(*this).dereference();
462 }
463 } else {
464 throw Type_error{"Cannot dereference a non pointer_type"};
465 }
466 }
467
472 operator ref_access_t<R, W> () const
473 {
474 return get();
475 }
476
481 ref_access_t<R, W> get() const
482 {
483 if (is_null()) throw Right_error{"Trying to dereference a null reference"};
484 return m_content->m_data;
485 }
486
491 ref_access_t<R, W> get(std::nothrow_t) const noexcept
492 {
493 if (is_null()) return nullptr;
494 return m_content->m_data;
495 }
496
500 template <class T>
501 T scalar_value() const
502 {
503 static_assert(R, "Cannot get scalar_value from Ref without read access");
504 if (auto&& scalar_type = std::dynamic_pointer_cast<const Scalar_datatype>(type())) {
505 if (scalar_type->kind() == PDI::Scalar_kind::UNSIGNED) {
506 switch (scalar_type->buffersize()) {
507 case 1L:
508 return *static_cast<const uint8_t*>(m_content->m_data);
509 case 2L:
510 return *static_cast<const uint16_t*>(m_content->m_data);
511 case 4L:
512 return *static_cast<const uint32_t*>(m_content->m_data);
513 case 8L:
514 return *static_cast<const uint64_t*>(m_content->m_data);
515 default:
516 throw Type_error{"Unknown size of unsigned integer datatype"};
517 }
518 } else if (scalar_type->kind() == PDI::Scalar_kind::SIGNED) {
519 switch (scalar_type->buffersize()) {
520 case 1L:
521 return *static_cast<const int8_t*>(m_content->m_data);
522 case 2L:
523 return *static_cast<const int16_t*>(m_content->m_data);
524 case 4L:
525 return *static_cast<const int32_t*>(m_content->m_data);
526 case 8L:
527 return *static_cast<const int64_t*>(m_content->m_data);
528 default:
529 throw Type_error{"Unknown size of integer datatype"};
530 }
531 } else if (scalar_type->kind() == PDI::Scalar_kind::FLOAT) {
532 switch (type()->buffersize()) {
533 case 4L: {
534 return *static_cast<const float*>(m_content->m_data);
535 }
536 case 8L: {
537 return *static_cast<const double*>(m_content->m_data);
538 }
539 default:
540 throw Type_error{"Unknown size of float datatype"};
541 }
542 } else {
543 throw Type_error{"Unknown datatype to get value"};
544 }
545 }
546 throw Type_error{"Expected scalar, found invalid type instead: {}", type()->debug_string()};
547 }
548
553 operator bool () const noexcept
554 {
555 return !is_null();
556 }
557
560 void reset() noexcept
561 {
562 if (m_content) unlink();
563 }
564
570 Ref copy() const
571 {
572 return do_copy(*this);
573 }
574
581 void* release() noexcept
582 {
583 if (is_null()) return nullptr;
584
585 // notify everybody of the nullification
586 while (!m_content->m_buffer->m_notifications.empty()) {
587 // get the key of a notification
588 const Reference_base* key = m_content->m_buffer->m_notifications.begin()->first;
589 // call this notification, this might invalidate any iterator
590 m_content->m_buffer->m_notifications.begin()->second(*this);
591 // remove the notification we just called
592 m_content->m_buffer->m_notifications.erase(key);
593 }
594
595 void* result = m_content->m_data;
596 m_content->m_data = nullptr;
597 m_content->m_buffer->m_deallocator = []() {
598 }; // Referenced_metadata won't delete data
599
600 unlink();
601
602 return result;
603 }
604
609 void on_nullify(std::function<void(Ref)> notifier) const noexcept
610 {
611 if (!is_null()) m_content->m_buffer->m_notifications[this] = notifier;
612 }
613
614private:
622 bool PDI_NO_EXPORT is_null() const noexcept
623 {
624 if (!m_content) return true;
625 if (!m_content->m_data) {
626 unlink();
627 return true;
628 }
629 return false;
630 }
631
636 void PDI_NO_EXPORT unlink() const noexcept
637 {
638 assert(m_content);
639 m_content->m_buffer->m_notifications.erase(this);
640 if (R || W) --m_content->m_buffer->m_write_locks;
641 if (W) --m_content->m_buffer->m_read_locks;
642 m_content.reset();
643 }
644
653 void PDI_NO_EXPORT link(std::shared_ptr<Referenced_data> content) noexcept
654 {
655 assert(!m_content);
656 if (!content || !content->m_data) return; // null ref
657 if ((R && content->m_buffer->m_read_locks) || (W && content->m_buffer->m_write_locks)) {
658 return;
659 }
660 m_content = std::move(content);
661 if (R || W) ++m_content->m_buffer->m_write_locks;
662 if (W) ++m_content->m_buffer->m_read_locks;
663 }
664};
665
666} // namespace PDI
667
668namespace std {
669
670template <bool R, bool W>
671struct hash<PDI::Ref_any<R, W>> {
672 size_t operator() (const PDI::Ref_any<R, W>& r) const noexcept
673 {
674 return r.hash();
675 }
676};
677
678} // namespace std
679
680#endif // PDI_REF_ANY_H_
A dynamically typed reference to data with automatic memory management and read/write locking semanti...
Definition: ref_any.h:248
Ref_any(void *data, std::function< void(void *)> freefunc, Datatype_sptr type, bool readable, bool writable)
Creates a reference to currently unreferenced data.
Definition: ref_any.h:304
Ref_any()=default
Constructs a null reference.
Ref_any(const Ref_any< OR, OW > &other) noexcept
Copies an existing reference with different privileges.
Definition: ref_any.h:276
Ref_any(Ref_any &&other) noexcept
Moves an existing reference.
Definition: ref_any.h:285
~Ref_any()
Destructor.
Definition: ref_any.h:317
void * release() noexcept
Releases ownership of the referenced raw data by nullifying all existing references.
Definition: ref_any.h:581
ref_access_t< R, W > get() const
Offers access to the referenced raw data, throws on null references.
Definition: ref_any.h:481
Ref copy() const
Makes a copy of the raw content behind this reference and returns a new reference.
Definition: ref_any.h:570
Ref_any(const Ref_any &other) noexcept
Copies an existing reference.
Definition: ref_any.h:263
ref_access_t< R, W > get(std::nothrow_t) const noexcept
Offers access to the referenced raw data, returns null for null references.
Definition: ref_any.h:491
T scalar_value() const
Returns a scalar value of type T taken from the data buffer.
Definition: ref_any.h:501
Ref dereference() const
Create a reference to the pointed content in case the ref type is a reference.
Definition: ref_any.h:448
void on_nullify(std::function< void(Ref)> notifier) const noexcept
Registers a nullification callback.
Definition: ref_any.h:609
void reset() noexcept
Nullify the reference.
Definition: ref_any.h:560
A common base for all references, whatever their access privileges.
Definition: ref_any.h:82
std::shared_ptr< Referenced_data > m_content
Pointer on the data content, can be null if the ref is null.
Definition: ref_any.h:195
Reference_base(Reference_base &&)=delete
Reference_base() noexcept
Constructs a null reference.
Definition: ref_any.h:212
Reference_base(const Reference_base &)=delete
static Ref do_copy(Ref_r ref)
Datatype_sptr type() const noexcept
accesses the type of the referenced raw data
static std::shared_ptr< Referenced_data > get_content(const Reference_base &other) noexcept
Function to access the content from a reference with different access right.
Definition: ref_any.h:199
Definition: error.h:187
Definition: error.h:200
Definition: array_datatype.h:38
std::shared_ptr< const Datatype > Datatype_sptr
Definition: pdi_fwd.h:78
STL namespace.
A descriptor for a buffer in which references can point.
Definition: ref_any.h:88
std::unordered_map< const Reference_base *, std::function< void(Ref)> > m_notifications
Nullification notifications registered on this instance.
Definition: ref_any.h:102
Referenced_buffer(const Referenced_buffer &)=delete
std::function< void()> m_deallocator
The function to call to deallocate the buffer memory.
Definition: ref_any.h:90
int m_write_locks
Number of locks preventing write access.
Definition: ref_any.h:99
Referenced_buffer(Referenced_buffer &&)=delete
int m_read_locks
Number of locks preventing read access.
Definition: ref_any.h:93
Referenced_buffer(std::function< void()> deleter, bool readable, bool writable) noexcept
Constructs a new buffer descriptor.
Definition: ref_any.h:110
~Referenced_buffer()
Definition: ref_any.h:125
A descriptor for data on which references can point.
Definition: ref_any.h:138
std::shared_ptr< Referenced_buffer > m_buffer
The buffer in which the data lives.
Definition: ref_any.h:140
Referenced_data(std::shared_ptr< Referenced_buffer > buffer, void *data, Datatype_sptr type)
Constructs a new data descriptor from an already referenced buffer.
Definition: ref_any.h:154
Datatype_sptr m_type
Type of the data.
Definition: ref_any.h:146
Referenced_data(void *data, std::function< void(void *)> freefunc, Datatype_sptr type, bool readable, bool writable)
Constructs a new data descriptor.
Definition: ref_any.h:171
Referenced_data(const Referenced_data &)=delete
void * m_data
In-memory location of the data.
Definition: ref_any.h:143
Referenced_data(Referenced_data &&)=delete